2018-07-26 20:27:35 +00:00
// Package mock contains mock implementations of different task interfaces.
package mock
import (
2018-08-01 18:54:32 +00:00
"bytes"
2018-07-26 20:27:35 +00:00
"context"
2018-08-01 18:54:32 +00:00
"encoding/binary"
2018-07-26 20:27:35 +00:00
"errors"
"fmt"
2018-08-23 22:47:56 +00:00
"strings"
2018-07-26 20:27:35 +00:00
"sync"
"time"
"github.com/influxdata/platform"
"github.com/influxdata/platform/task/backend"
scheduler "github.com/influxdata/platform/task/backend"
"go.uber.org/zap"
)
// Scheduler is a mock implementation of a task scheduler.
type Scheduler struct {
sync . Mutex
lastTick int64
claims map [ string ] * Task
2018-08-17 18:03:12 +00:00
meta map [ string ] backend . StoreTaskMeta
2018-07-26 20:27:35 +00:00
createChan chan * Task
releaseChan chan * Task
claimError error
releaseError error
}
// Task is a mock implementation of a task.
type Task struct {
Script string
StartExecution int64
ConcurrencyLimit uint8
}
func NewScheduler ( ) * Scheduler {
return & Scheduler {
claims : map [ string ] * Task { } ,
2018-08-17 18:03:12 +00:00
meta : map [ string ] backend . StoreTaskMeta { } ,
2018-07-26 20:27:35 +00:00
}
}
func ( s * Scheduler ) Tick ( now int64 ) {
s . Lock ( )
defer s . Unlock ( )
s . lastTick = now
}
func ( s * Scheduler ) WithLogger ( l * zap . Logger ) { }
2018-08-17 18:03:12 +00:00
func ( s * Scheduler ) ClaimTask ( task * backend . StoreTask , meta * backend . StoreTaskMeta ) error {
2018-07-26 20:27:35 +00:00
if s . claimError != nil {
return s . claimError
}
s . Lock ( )
defer s . Unlock ( )
2018-08-01 17:36:10 +00:00
_ , ok := s . claims [ task . ID . String ( ) ]
2018-07-26 20:27:35 +00:00
if ok {
return errors . New ( "task already in list" )
}
2018-08-17 18:03:12 +00:00
s . meta [ task . ID . String ( ) ] = * meta
2018-07-26 20:27:35 +00:00
2018-08-21 22:52:18 +00:00
t := & Task { Script : task . Script , StartExecution : meta . LatestCompleted , ConcurrencyLimit : uint8 ( meta . MaxConcurrency ) }
2018-07-26 20:27:35 +00:00
2018-08-01 17:36:10 +00:00
s . claims [ task . ID . String ( ) ] = t
2018-07-26 20:27:35 +00:00
if s . createChan != nil {
s . createChan <- t
}
return nil
}
func ( s * Scheduler ) ReleaseTask ( taskID platform . ID ) error {
if s . releaseError != nil {
return s . releaseError
}
s . Lock ( )
defer s . Unlock ( )
t , ok := s . claims [ taskID . String ( ) ]
if ! ok {
return errors . New ( "task not in list" )
}
if s . releaseChan != nil {
s . releaseChan <- t
}
delete ( s . claims , taskID . String ( ) )
2018-08-17 18:03:12 +00:00
delete ( s . meta , taskID . String ( ) )
2018-07-26 20:27:35 +00:00
return nil
}
func ( s * Scheduler ) TaskFor ( id platform . ID ) * Task {
return s . claims [ id . String ( ) ]
}
func ( s * Scheduler ) TaskCreateChan ( ) <- chan * Task {
s . createChan = make ( chan * Task , 10 )
return s . createChan
}
func ( s * Scheduler ) TaskReleaseChan ( ) <- chan * Task {
s . releaseChan = make ( chan * Task , 10 )
return s . releaseChan
}
// ClaimError sets an error to be returned by s.ClaimTask, if err is not nil.
func ( s * Scheduler ) ClaimError ( err error ) {
s . claimError = err
}
// ReleaseError sets an error to be returned by s.ReleaseTask, if err is not nil.
func ( s * Scheduler ) ReleaseError ( err error ) {
s . releaseError = err
}
// DesiredState is a mock implementation of DesiredState (used by NewScheduler).
type DesiredState struct {
mu sync . Mutex
// Map of stringified task ID to last ID used for run.
2018-08-01 18:54:32 +00:00
runIDs map [ string ] uint32
2018-07-26 20:27:35 +00:00
// Map of stringified, concatenated task and platform ID, to runs that have been created.
2018-08-14 21:24:15 +00:00
created map [ string ] backend . QueuedRun
2018-08-17 18:03:12 +00:00
// Map of stringified task ID to task meta.
meta map [ string ] backend . StoreTaskMeta
2018-07-26 20:27:35 +00:00
}
var _ backend . DesiredState = ( * DesiredState ) ( nil )
func NewDesiredState ( ) * DesiredState {
return & DesiredState {
2018-08-01 18:54:32 +00:00
runIDs : make ( map [ string ] uint32 ) ,
2018-08-14 21:24:15 +00:00
created : make ( map [ string ] backend . QueuedRun ) ,
2018-08-17 18:03:12 +00:00
meta : make ( map [ string ] backend . StoreTaskMeta ) ,
2018-07-26 20:27:35 +00:00
}
}
2018-08-17 18:03:12 +00:00
// SetTaskMeta sets the task meta for the given task ID.
// SetTaskMeta must be called before CreateNextRun, for a given task ID.
func ( d * DesiredState ) SetTaskMeta ( taskID platform . ID , meta backend . StoreTaskMeta ) {
d . mu . Lock ( )
defer d . mu . Unlock ( )
d . meta [ taskID . String ( ) ] = meta
}
// CreateNextRun creates the next run for the given task.
// Refer to the documentation for SetTaskPeriod to understand how the times are determined.
func ( d * DesiredState ) CreateNextRun ( _ context . Context , taskID platform . ID , now int64 ) ( backend . RunCreation , error ) {
d . mu . Lock ( )
defer d . mu . Unlock ( )
tid := taskID . String ( )
meta , ok := d . meta [ tid ]
if ! ok {
panic ( fmt . Sprintf ( "meta not set for task with ID %s" , tid ) )
}
makeID := func ( ) ( platform . ID , error ) {
d . runIDs [ tid ] ++
runID := make ( [ ] byte , 4 )
binary . BigEndian . PutUint32 ( runID , d . runIDs [ tid ] )
return platform . ID ( runID ) , nil
}
rc , err := meta . CreateNextRun ( now , makeID )
if err != nil {
return backend . RunCreation { } , err
}
d . meta [ tid ] = meta
rc . Created . TaskID = append ( [ ] byte ( nil ) , taskID ... )
d . created [ tid + rc . Created . RunID . String ( ) ] = rc . Created
return rc , nil
}
2018-07-26 20:27:35 +00:00
func ( d * DesiredState ) FinishRun ( _ context . Context , taskID , runID platform . ID ) error {
d . mu . Lock ( )
defer d . mu . Unlock ( )
2018-08-23 22:47:56 +00:00
tid := taskID . String ( )
rid := runID . String ( )
m := d . meta [ tid ]
if ! m . FinishRun ( runID ) {
var knownIDs [ ] string
for _ , r := range m . CurrentlyRunning {
knownIDs = append ( knownIDs , platform . ID ( r . RunID ) . String ( ) )
}
return fmt . Errorf ( "unknown run ID %s; known run IDs: %s" , rid , strings . Join ( knownIDs , ", " ) )
}
d . meta [ tid ] = m
delete ( d . created , tid + rid )
2018-07-26 20:27:35 +00:00
return nil
}
2018-08-14 21:24:15 +00:00
func ( d * DesiredState ) CreatedFor ( taskID platform . ID ) [ ] backend . QueuedRun {
2018-07-26 20:27:35 +00:00
d . mu . Lock ( )
defer d . mu . Unlock ( )
2018-08-14 21:24:15 +00:00
var qrs [ ] backend . QueuedRun
2018-07-26 20:27:35 +00:00
for _ , qr := range d . created {
2018-08-01 18:54:32 +00:00
if bytes . Equal ( qr . TaskID , taskID ) {
2018-07-26 20:27:35 +00:00
qrs = append ( qrs , qr )
}
}
return qrs
}
// PollForNumberCreated blocks for a small amount of time waiting for exactly the given count of created runs for the given task ID.
// If the expected number isn't found in time, it returns an error.
//
// Because the scheduler and executor do a lot of state changes asynchronously, this is useful in test.
2018-08-14 21:24:15 +00:00
func ( d * DesiredState ) PollForNumberCreated ( taskID platform . ID , count int ) ( [ ] scheduler . QueuedRun , error ) {
2018-07-26 20:27:35 +00:00
const numAttempts = 50
actualCount := 0
2018-08-14 21:24:15 +00:00
var created [ ] scheduler . QueuedRun
2018-07-26 20:27:35 +00:00
for i := 0 ; i < numAttempts ; i ++ {
time . Sleep ( 2 * time . Millisecond ) // we sleep even on first so it becomes more likely that we catch when too many are produced.
created = d . CreatedFor ( taskID )
actualCount = len ( created )
if actualCount == count {
return created , nil
}
}
return created , fmt . Errorf ( "did not see count of %d created task(s) for ID %s in time, instead saw %d" , count , taskID . String ( ) , actualCount ) // we return created anyways, to make it easier to debug
}
type Executor struct {
mu sync . Mutex
// Map of stringified, concatenated task and run ID, to runs that have begun execution but have not finished.
running map [ string ] * RunPromise
// Map of stringified, concatenated task and run ID, to results of runs that have executed and completed.
finished map [ string ] backend . RunResult
}
var _ backend . Executor = ( * Executor ) ( nil )
func NewExecutor ( ) * Executor {
return & Executor {
running : make ( map [ string ] * RunPromise ) ,
finished : make ( map [ string ] backend . RunResult ) ,
}
}
2018-08-14 21:24:15 +00:00
func ( e * Executor ) Execute ( _ context . Context , run backend . QueuedRun ) ( backend . RunPromise , error ) {
2018-07-26 20:27:35 +00:00
rp := NewRunPromise ( run )
id := run . TaskID . String ( ) + run . RunID . String ( )
e . mu . Lock ( )
e . running [ id ] = rp
e . mu . Unlock ( )
go func ( ) {
res , _ := rp . Wait ( )
e . mu . Lock ( )
delete ( e . running , id )
e . finished [ id ] = res
e . mu . Unlock ( )
} ( )
return rp , nil
}
func ( e * Executor ) WithLogger ( l * zap . Logger ) { }
// RunningFor returns the run promises for the given task.
func ( e * Executor ) RunningFor ( taskID platform . ID ) [ ] * RunPromise {
e . mu . Lock ( )
defer e . mu . Unlock ( )
var rps [ ] * RunPromise
for _ , rp := range e . running {
2018-08-01 18:54:32 +00:00
if bytes . Equal ( rp . Run ( ) . TaskID , taskID ) {
2018-07-26 20:27:35 +00:00
rps = append ( rps , rp )
}
}
return rps
}
// PollForNumberRunning blocks for a small amount of time waiting for exactly the given count of active runs for the given task ID.
// If the expected number isn't found in time, it returns an error.
//
// Because the scheduler and executor do a lot of state changes asynchronously, this is useful in test.
func ( e * Executor ) PollForNumberRunning ( taskID platform . ID , count int ) ( [ ] * RunPromise , error ) {
const numAttempts = 20
2018-08-23 22:47:56 +00:00
var running [ ] * RunPromise
2018-07-26 20:27:35 +00:00
for i := 0 ; i < numAttempts ; i ++ {
if i > 0 {
time . Sleep ( 10 * time . Millisecond )
}
2018-08-23 22:47:56 +00:00
running = e . RunningFor ( taskID )
if len ( running ) == count {
2018-07-26 20:27:35 +00:00
return running , nil
}
}
2018-08-23 22:47:56 +00:00
return nil , fmt . Errorf ( "did not see count of %d running task(s) for ID %s in time; last count was %d" , count , taskID . String ( ) , len ( running ) )
2018-07-26 20:27:35 +00:00
}
// RunPromise is a mock RunPromise.
type RunPromise struct {
2018-08-14 21:24:15 +00:00
qr backend . QueuedRun
2018-07-26 20:27:35 +00:00
setResultOnce sync . Once
mu sync . Mutex
res backend . RunResult
err error
}
var _ backend . RunPromise = ( * RunPromise ) ( nil )
2018-08-14 21:24:15 +00:00
func NewRunPromise ( qr backend . QueuedRun ) * RunPromise {
2018-07-26 20:27:35 +00:00
p := & RunPromise {
qr : qr ,
}
p . mu . Lock ( ) // Locked so calls to Wait will block until setResultOnce is called.
return p
}
2018-08-14 21:24:15 +00:00
func ( p * RunPromise ) Run ( ) backend . QueuedRun {
2018-07-26 20:27:35 +00:00
return p . qr
}
func ( p * RunPromise ) Wait ( ) ( backend . RunResult , error ) {
p . mu . Lock ( )
defer p . mu . Unlock ( )
return p . res , p . err
}
func ( p * RunPromise ) Cancel ( ) {
p . Finish ( nil , backend . ErrRunCanceled )
}
// Finish unblocks any call to Wait, to return r and err.
// Only the first call to Finish has any effect.
func ( p * RunPromise ) Finish ( r backend . RunResult , err error ) {
p . setResultOnce . Do ( func ( ) {
p . res , p . err = r , err
p . mu . Unlock ( )
} )
}
// RunResult is a mock implementation of RunResult.
type RunResult struct {
err error
isRetryable bool
}
var _ backend . RunResult = ( * RunResult ) ( nil )
func NewRunResult ( err error , isRetryable bool ) * RunResult {
return & RunResult { err : err , isRetryable : isRetryable }
}
func ( rr * RunResult ) Err ( ) error {
return rr . err
}
func ( rr * RunResult ) IsRetryable ( ) bool {
return rr . isRetryable
}