runner/runners/chaos.go (86 lines of code) (raw):

package runners import ( "math/rand" "sync" "time" "github.com/twitter/scoot/runner" ) // chaos.go: impl that introduces errors (for testing) // Creates a new Chaos Runner func NewChaosRunner(delegate runner.Service) *ChaosRunner { runner := &ChaosRunner{del: delegate} return runner } // ChaosRunner implements Runner by calling to a delegate runner in the happy path, // but delaying a random time between 0 and MaxDelay, or returning an error type ChaosRunner struct { del runner.Service mu sync.Mutex maxDelay time.Duration err error runStatus runner.RunStatus } // Chaos Controls // SetError sets the error to return on calls to the Runner func (r *ChaosRunner) SetError(err error) { r.mu.Lock() defer r.mu.Unlock() r.err = err } func (r *ChaosRunner) SetRunStatus(status runner.RunStatus) { r.runStatus = status } // SetDelay sets the max delay to delay; the actual delay will be randomly selected up to delay func (r *ChaosRunner) SetDelay(delay time.Duration) { r.mu.Lock() defer r.mu.Unlock() r.maxDelay = delay } // Chaos implementations func (r *ChaosRunner) delay() error { r.mu.Lock() defer r.mu.Unlock() if r.maxDelay != 0 { time.Sleep(time.Duration(rand.Int63n(int64(r.maxDelay)))) } return r.err } // Implement Runner func (r *ChaosRunner) Run(cmd *runner.Command) (runner.RunStatus, error) { err := r.delay() if err != nil { return r.runStatus, err } return r.del.Run(cmd) } func (r *ChaosRunner) Query(q runner.Query, w runner.Wait) ([]runner.RunStatus, runner.ServiceStatus, error) { err := r.delay() if err != nil { return nil, runner.ServiceStatus{}, err } return r.del.Query(q, w) } func (r *ChaosRunner) QueryNow(q runner.Query) ([]runner.RunStatus, runner.ServiceStatus, error) { err := r.delay() if err != nil { return nil, runner.ServiceStatus{}, err } return r.del.QueryNow(q) } func (r *ChaosRunner) Status(run runner.RunID) (runner.RunStatus, runner.ServiceStatus, error) { err := r.delay() if err != nil { return runner.RunStatus{}, runner.ServiceStatus{}, err } return r.del.Status(run) } func (r *ChaosRunner) StatusAll() ([]runner.RunStatus, runner.ServiceStatus, error) { err := r.delay() if err != nil { return nil, runner.ServiceStatus{}, err } return r.del.StatusAll() } func (r *ChaosRunner) Abort(run runner.RunID) (runner.RunStatus, error) { err := r.delay() if err != nil { return runner.RunStatus{}, err } return r.del.Abort(run) } func (r *ChaosRunner) Release() { // Always delegate release if del is present. if r.del != nil { r.del.Release() } }