2016-04-20 08:24:17 +02:00
package job
import (
"fmt"
2016-05-11 10:05:19 +02:00
"sync"
2016-04-20 08:24:17 +02:00
"github.com/vmware/harbor/dao"
2016-05-13 15:43:17 +02:00
"github.com/vmware/harbor/job/config"
2016-05-11 10:05:19 +02:00
"github.com/vmware/harbor/job/imgout"
"github.com/vmware/harbor/job/utils"
2016-05-10 13:38:50 +02:00
"github.com/vmware/harbor/models"
2016-04-20 08:24:17 +02:00
"github.com/vmware/harbor/utils/log"
)
2016-05-10 13:38:50 +02:00
type RepJobParm struct {
LocalRegURL string
TargetURL string
TargetUsername string
TargetPassword string
Repository string
Enabled int
Operation string
2016-04-20 08:24:17 +02:00
}
type JobSM struct {
JobID int64
CurrentState string
PreviousState string
//The states that don't have to exist in transition map, such as "Error", "Canceled"
ForcedStates map [ string ] struct { }
Transitions map [ string ] map [ string ] struct { }
Handlers map [ string ] StateHandler
2016-05-03 09:51:52 +02:00
desiredState string
2016-05-11 10:05:19 +02:00
Logger utils . Logger
2016-05-10 13:38:50 +02:00
Parms * RepJobParm
lock * sync . Mutex
2016-04-20 08:24:17 +02:00
}
2016-05-03 09:51:52 +02:00
// EnsterState transit the statemachine from the current state to the state in parameter.
// It returns the next state the statemachine should tranit to.
func ( sm * JobSM ) EnterState ( s string ) ( string , error ) {
2016-05-19 10:09:44 +02:00
log . Debugf ( "Job id: %d, transiting from State: %s, to State: %s" , sm . JobID , sm . CurrentState , s )
2016-04-20 08:24:17 +02:00
targets , ok := sm . Transitions [ sm . CurrentState ]
_ , exist := targets [ s ]
_ , isForced := sm . ForcedStates [ s ]
if ! exist && ! isForced {
2016-05-19 10:09:44 +02:00
return "" , fmt . Errorf ( "Job id: %d, transition from %s to %s does not exist!" , sm . JobID , sm . CurrentState , s )
2016-04-20 08:24:17 +02:00
}
exitHandler , ok := sm . Handlers [ sm . CurrentState ]
if ok {
if err := exitHandler . Exit ( ) ; err != nil {
2016-05-03 09:51:52 +02:00
return "" , err
2016-04-20 08:24:17 +02:00
}
} else {
2016-05-19 10:09:44 +02:00
log . Debugf ( "Job id: %d, no handler found for state:%s, skip" , sm . JobID , sm . CurrentState )
2016-04-20 08:24:17 +02:00
}
enterHandler , ok := sm . Handlers [ s ]
2016-05-10 13:38:50 +02:00
var next string = models . JobContinue
2016-05-03 09:51:52 +02:00
var err error
2016-04-20 08:24:17 +02:00
if ok {
2016-05-03 09:51:52 +02:00
if next , err = enterHandler . Enter ( ) ; err != nil {
return "" , err
2016-04-20 08:24:17 +02:00
}
} else {
2016-05-19 10:09:44 +02:00
log . Debugf ( "Job id: %d, no handler found for state:%s, skip" , sm . JobID , s )
2016-04-20 08:24:17 +02:00
}
sm . PreviousState = sm . CurrentState
sm . CurrentState = s
2016-05-19 10:09:44 +02:00
log . Debugf ( "Job id: %d, transition succeeded, current state: %s" , sm . JobID , s )
2016-05-03 09:51:52 +02:00
return next , nil
}
// Start kicks off the statemachine to transit from current state to s, and moves on
// It will search the transit map if the next state is "_continue", and
// will enter error state if there's more than one possible path when next state is "_continue"
func ( sm * JobSM ) Start ( s string ) {
n , err := sm . EnterState ( s )
2016-05-19 10:09:44 +02:00
log . Debugf ( "Job id: %d, next state from handler: %s" , sm . JobID , n )
2016-05-03 09:51:52 +02:00
for len ( n ) > 0 && err == nil {
if d := sm . getDesiredState ( ) ; len ( d ) > 0 {
2016-05-19 10:09:44 +02:00
log . Debugf ( "Job id: %d. Desired state: %s, will ignore the next state from handler" , sm . JobID , d )
2016-05-03 09:51:52 +02:00
n = d
sm . setDesiredState ( "" )
continue
}
2016-05-10 13:38:50 +02:00
if n == models . JobContinue && len ( sm . Transitions [ sm . CurrentState ] ) == 1 {
2016-05-03 09:51:52 +02:00
for n = range sm . Transitions [ sm . CurrentState ] {
break
}
2016-05-19 10:09:44 +02:00
log . Debugf ( "Job id: %d, Continue to state: %s" , sm . JobID , n )
2016-05-03 09:51:52 +02:00
continue
}
2016-05-10 13:38:50 +02:00
if n == models . JobContinue && len ( sm . Transitions [ sm . CurrentState ] ) != 1 {
2016-05-19 10:09:44 +02:00
log . Errorf ( "Job id: %d, next state is continue but there are %d possible next states in transition table" , sm . JobID , len ( sm . Transitions [ sm . CurrentState ] ) )
2016-05-03 09:51:52 +02:00
err = fmt . Errorf ( "Unable to continue" )
break
}
n , err = sm . EnterState ( n )
2016-05-19 10:09:44 +02:00
log . Debugf ( "Job id: %d, next state from handler: %s" , sm . JobID , n )
2016-05-03 09:51:52 +02:00
}
if err != nil {
2016-05-19 10:09:44 +02:00
log . Warningf ( "Job id: %d, the statemachin will enter error state due to error: %v" , sm . JobID , err )
2016-05-10 13:38:50 +02:00
sm . EnterState ( models . JobError )
2016-05-03 09:51:52 +02:00
}
2016-04-20 08:24:17 +02:00
}
func ( sm * JobSM ) AddTransition ( from string , to string , h StateHandler ) {
_ , ok := sm . Transitions [ from ]
if ! ok {
sm . Transitions [ from ] = make ( map [ string ] struct { } )
}
sm . Transitions [ from ] [ to ] = struct { } { }
sm . Handlers [ to ] = h
}
func ( sm * JobSM ) RemoveTransition ( from string , to string ) {
_ , ok := sm . Transitions [ from ]
if ! ok {
return
}
delete ( sm . Transitions [ from ] , to )
}
2016-05-19 10:09:44 +02:00
func ( sm * JobSM ) Stop ( id int64 ) {
log . Debugf ( "Trying to stop the job: %d" , id )
sm . lock . Lock ( )
defer sm . lock . Unlock ( )
//need to check if the sm switched to other job
if id == sm . JobID {
sm . desiredState = models . JobStopped
log . Debugf ( "Desired state of job %d is set to stopped" , id )
} else {
log . Debugf ( "State machine has switched to job %d, so the action to stop job %d will be ignored" , sm . JobID , id )
}
2016-05-03 09:51:52 +02:00
}
func ( sm * JobSM ) getDesiredState ( ) string {
sm . lock . Lock ( )
defer sm . lock . Unlock ( )
return sm . desiredState
}
func ( sm * JobSM ) setDesiredState ( s string ) {
sm . lock . Lock ( )
defer sm . lock . Unlock ( )
sm . desiredState = s
}
2016-05-13 15:43:17 +02:00
func ( sm * JobSM ) Init ( ) {
sm . lock = & sync . Mutex { }
sm . Handlers = make ( map [ string ] StateHandler )
sm . Transitions = make ( map [ string ] map [ string ] struct { } )
2016-05-19 10:09:44 +02:00
sm . ForcedStates = map [ string ] struct { } {
models . JobError : struct { } { } ,
models . JobStopped : struct { } { } ,
models . JobCanceled : struct { } { } ,
}
2016-05-13 15:43:17 +02:00
}
2016-05-19 10:09:44 +02:00
2016-05-13 15:43:17 +02:00
func ( sm * JobSM ) Reset ( jid int64 ) error {
2016-05-19 10:09:44 +02:00
//To ensure the new jobID is visible to the thread to stop the SM
sm . lock . Lock ( )
2016-05-13 15:43:17 +02:00
sm . JobID = jid
2016-05-19 10:09:44 +02:00
sm . desiredState = ""
sm . lock . Unlock ( )
2016-05-16 07:57:30 +02:00
2016-05-10 13:38:50 +02:00
//init parms
job , err := dao . GetRepJob ( sm . JobID )
if err != nil {
return fmt . Errorf ( "Failed to get job, error: %v" , err )
}
if job == nil {
return fmt . Errorf ( "The job doesn't exist in DB, job id: %d" , sm . JobID )
}
policy , err := dao . GetRepPolicy ( job . PolicyID )
if err != nil {
return fmt . Errorf ( "Failed to get policy, error: %v" , err )
}
if policy == nil {
return fmt . Errorf ( "The policy doesn't exist in DB, policy id:%d" , job . PolicyID )
}
sm . Parms = & RepJobParm {
2016-05-13 15:43:17 +02:00
LocalRegURL : config . LocalRegURL ( ) ,
2016-05-10 13:38:50 +02:00
Repository : job . Repository ,
Enabled : policy . Enabled ,
Operation : job . Operation ,
}
if policy . Enabled == 0 {
2016-05-19 10:09:44 +02:00
//worker will cancel this job
2016-05-10 13:38:50 +02:00
return nil
}
target , err := dao . GetRepTarget ( policy . TargetID )
if err != nil {
return fmt . Errorf ( "Failed to get target, error: %v" , err )
}
if target == nil {
return fmt . Errorf ( "The target doesn't exist in DB, target id: %d" , policy . TargetID )
}
sm . Parms . TargetURL = target . URL
sm . Parms . TargetUsername = target . Username
sm . Parms . TargetPassword = target . Password
//init states handlers
2016-05-11 10:05:19 +02:00
sm . Logger = utils . Logger { sm . JobID }
2016-05-10 13:38:50 +02:00
sm . CurrentState = models . JobPending
2016-05-19 10:09:44 +02:00
sm . AddTransition ( models . JobPending , models . JobRunning , StatusUpdater { DummyHandler { JobID : sm . JobID } , models . JobRunning } )
sm . Handlers [ models . JobError ] = StatusUpdater { DummyHandler { JobID : sm . JobID } , models . JobError }
sm . Handlers [ models . JobStopped ] = StatusUpdater { DummyHandler { JobID : sm . JobID } , models . JobStopped }
2016-05-10 13:38:50 +02:00
if sm . Parms . Operation == models . RepOpTransfer {
2016-05-11 10:05:19 +02:00
/ *
sm . AddTransition ( models . JobRunning , "pull-img" , ImgPuller { DummyHandler : DummyHandler { JobID : sm . JobID } , img : sm . Parms . Repository , logger : sm . Logger } )
2016-05-19 10:09:44 +02:00
//only handle one target for now
2016-05-11 10:05:19 +02:00
sm . AddTransition ( "pull-img" , "push-img" , ImgPusher { DummyHandler : DummyHandler { JobID : sm . JobID } , targetURL : sm . Parms . TargetURL , logger : sm . Logger } )
sm . AddTransition ( "push-img" , models . JobFinished , StatusUpdater { DummyHandler { JobID : sm . JobID } , models . JobFinished } )
* /
2016-05-19 10:09:44 +02:00
2016-05-11 10:05:19 +02:00
if err = addImgOutTransition ( sm ) ; err != nil {
return err
}
2016-05-19 10:09:44 +02:00
2016-05-10 13:38:50 +02:00
}
return nil
2016-04-20 08:24:17 +02:00
}
2016-05-11 10:05:19 +02:00
func addImgOutTransition ( sm * JobSM ) error {
base , err := imgout . InitBaseHandler ( sm . Parms . Repository , sm . Parms . LocalRegURL , "" ,
sm . Parms . TargetURL , sm . Parms . TargetUsername , sm . Parms . TargetPassword ,
nil , & sm . Logger )
if err != nil {
return err
}
sm . AddTransition ( models . JobRunning , imgout . StateCheck , & imgout . Checker { BaseHandler : base } )
sm . AddTransition ( imgout . StateCheck , imgout . StatePullManifest , & imgout . ManifestPuller { BaseHandler : base } )
sm . AddTransition ( imgout . StatePullManifest , imgout . StateTransferBlob , & imgout . BlobTransfer { BaseHandler : base } )
sm . AddTransition ( imgout . StatePullManifest , models . JobFinished , & StatusUpdater { DummyHandler { JobID : sm . JobID } , models . JobFinished } )
sm . AddTransition ( imgout . StateTransferBlob , imgout . StatePushManifest , & imgout . ManifestPusher { BaseHandler : base } )
sm . AddTransition ( imgout . StatePushManifest , imgout . StatePullManifest , & imgout . ManifestPuller { BaseHandler : base } )
return nil
}