mirror of
https://github.com/goharbor/harbor
synced 2025-04-10 05:00:55 +00:00
statemachine and schduler to support retry
This commit is contained in:
parent
9052206b99
commit
6b955cd154
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -6,9 +6,4 @@ Deploy/config/db/env
|
||||||
Deploy/config/jobservice/env
|
Deploy/config/jobservice/env
|
||||||
ui/ui
|
ui/ui
|
||||||
*.pyc
|
*.pyc
|
||||||
jobservice/*.sql
|
jobservice/test
|
||||||
jobservice/*.sh
|
|
||||||
jobservice/*.json
|
|
||||||
jobservice/jobservice
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,36 @@
|
||||||
/*
|
/*
|
||||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package job
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vmware/harbor/utils/log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var jobQueue = make(chan int64)
|
var jobQueue = make(chan int64)
|
||||||
|
|
||||||
// Schedule put a job id into job queue.
|
// Schedule put a job id into job queue.
|
||||||
func Schedule(jobID int64) {
|
func Schedule(jobID int64) {
|
||||||
jobQueue <- jobID
|
jobQueue <- jobID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reschedule is called by statemachine to retry a job
|
||||||
|
func Reschedule(jobID int64) {
|
||||||
|
log.Debugf("Job %d will be rescheduled in 5 minutes", jobID)
|
||||||
|
time.Sleep(5 * time.Minute)
|
||||||
|
log.Debugf("Rescheduling job %d", jobID)
|
||||||
|
Schedule(jobID)
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
/*
|
/*
|
||||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package job
|
package job
|
||||||
|
@ -33,25 +33,10 @@ type StateHandler interface {
|
||||||
Exit() error
|
Exit() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// DummyHandler is the default implementation of StateHander interface, which has empty Enter and Exit methods.
|
|
||||||
type DummyHandler struct {
|
|
||||||
JobID int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enter ...
|
|
||||||
func (dh DummyHandler) Enter() (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit ...
|
|
||||||
func (dh DummyHandler) Exit() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatusUpdater implements the StateHandler interface which updates the status of a job in DB when the job enters
|
// StatusUpdater implements the StateHandler interface which updates the status of a job in DB when the job enters
|
||||||
// a status.
|
// a status.
|
||||||
type StatusUpdater struct {
|
type StatusUpdater struct {
|
||||||
DummyHandler
|
JobID int64
|
||||||
State string
|
State string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,9 +54,34 @@ func (su StatusUpdater) Enter() (string, error) {
|
||||||
return next, err
|
return next, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exit ...
|
||||||
|
func (su StatusUpdater) Exit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry handles a special "retrying" in which case it will update the status in DB and reschedule the job
|
||||||
|
// via scheduler
|
||||||
|
type Retry struct {
|
||||||
|
JobID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter ...
|
||||||
|
func (jr Retry) Enter() (string, error) {
|
||||||
|
err := dao.UpdateRepJobStatus(jr.JobID, models.JobRetrying)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to update state of job :%d to Retrying, error: %v", jr.JobID, err)
|
||||||
|
}
|
||||||
|
go Reschedule(jr.JobID)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit ...
|
||||||
|
func (jr Retry) Exit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ImgPuller was for testing
|
// ImgPuller was for testing
|
||||||
type ImgPuller struct {
|
type ImgPuller struct {
|
||||||
DummyHandler
|
|
||||||
img string
|
img string
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
@ -80,13 +90,17 @@ type ImgPuller struct {
|
||||||
func (ip ImgPuller) Enter() (string, error) {
|
func (ip ImgPuller) Enter() (string, error) {
|
||||||
ip.logger.Infof("I'm pretending to pull img:%s, then sleep 30s", ip.img)
|
ip.logger.Infof("I'm pretending to pull img:%s, then sleep 30s", ip.img)
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
ip.logger.Infof("wake up from sleep....")
|
ip.logger.Infof("wake up from sleep.... testing retry")
|
||||||
return "push-img", nil
|
return models.JobRetrying, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit ...
|
||||||
|
func (ip ImgPuller) Exit() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImgPusher is a statehandler for testing
|
// ImgPusher is a statehandler for testing
|
||||||
type ImgPusher struct {
|
type ImgPusher struct {
|
||||||
DummyHandler
|
|
||||||
targetURL string
|
targetURL string
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
@ -95,6 +109,11 @@ type ImgPusher struct {
|
||||||
func (ip ImgPusher) Enter() (string, error) {
|
func (ip ImgPusher) Enter() (string, error) {
|
||||||
ip.logger.Infof("I'm pretending to push img to:%s, then sleep 30s", ip.targetURL)
|
ip.logger.Infof("I'm pretending to push img to:%s, then sleep 30s", ip.targetURL)
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
ip.logger.Infof("wake up from sleep....")
|
ip.logger.Infof("wake up from sleep.... testing retry")
|
||||||
return models.JobContinue, nil
|
return models.JobRetrying, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit ...
|
||||||
|
func (ip ImgPusher) Exit() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,6 +179,7 @@ func (sm *SM) Init() {
|
||||||
models.JobError: struct{}{},
|
models.JobError: struct{}{},
|
||||||
models.JobStopped: struct{}{},
|
models.JobStopped: struct{}{},
|
||||||
models.JobCanceled: struct{}{},
|
models.JobCanceled: struct{}{},
|
||||||
|
models.JobRetrying: struct{}{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,9 +244,11 @@ func (sm *SM) Reset(jid int64) error {
|
||||||
sm.Transitions = make(map[string]map[string]struct{})
|
sm.Transitions = make(map[string]map[string]struct{})
|
||||||
sm.CurrentState = models.JobPending
|
sm.CurrentState = models.JobPending
|
||||||
|
|
||||||
sm.AddTransition(models.JobPending, models.JobRunning, StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobRunning})
|
sm.AddTransition(models.JobPending, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning})
|
||||||
sm.Handlers[models.JobError] = StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobError}
|
sm.AddTransition(models.JobRetrying, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning})
|
||||||
sm.Handlers[models.JobStopped] = StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobStopped}
|
sm.Handlers[models.JobError] = StatusUpdater{sm.JobID, models.JobError}
|
||||||
|
sm.Handlers[models.JobStopped] = StatusUpdater{sm.JobID, models.JobStopped}
|
||||||
|
sm.Handlers[models.JobRetrying] = Retry{sm.JobID}
|
||||||
|
|
||||||
switch sm.Parms.Operation {
|
switch sm.Parms.Operation {
|
||||||
case models.RepOpTransfer:
|
case models.RepOpTransfer:
|
||||||
|
@ -259,6 +262,12 @@ func (sm *SM) Reset(jid int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//for testing onlly
|
||||||
|
func addTestTransition(sm *SM) error {
|
||||||
|
sm.AddTransition(models.JobRunning, "pull-img", ImgPuller{img: sm.Parms.Repository, logger: sm.Logger})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func addImgTransferTransition(sm *SM) error {
|
func addImgTransferTransition(sm *SM) error {
|
||||||
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
||||||
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
||||||
|
@ -269,7 +278,7 @@ func addImgTransferTransition(sm *SM) error {
|
||||||
sm.AddTransition(models.JobRunning, replication.StateCheck, &replication.Checker{BaseHandler: base})
|
sm.AddTransition(models.JobRunning, replication.StateCheck, &replication.Checker{BaseHandler: base})
|
||||||
sm.AddTransition(replication.StateCheck, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
|
sm.AddTransition(replication.StateCheck, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
|
||||||
sm.AddTransition(replication.StatePullManifest, replication.StateTransferBlob, &replication.BlobTransfer{BaseHandler: base})
|
sm.AddTransition(replication.StatePullManifest, replication.StateTransferBlob, &replication.BlobTransfer{BaseHandler: base})
|
||||||
sm.AddTransition(replication.StatePullManifest, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})
|
sm.AddTransition(replication.StatePullManifest, models.JobFinished, &StatusUpdater{sm.JobID, models.JobFinished})
|
||||||
sm.AddTransition(replication.StateTransferBlob, replication.StatePushManifest, &replication.ManifestPusher{BaseHandler: base})
|
sm.AddTransition(replication.StateTransferBlob, replication.StatePushManifest, &replication.ManifestPusher{BaseHandler: base})
|
||||||
sm.AddTransition(replication.StatePushManifest, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
|
sm.AddTransition(replication.StatePushManifest, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
|
||||||
return nil
|
return nil
|
||||||
|
@ -283,7 +292,7 @@ func addImgDeleteTransition(sm *SM) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.AddTransition(models.JobRunning, replication.StateDelete, deleter)
|
sm.AddTransition(models.JobRunning, replication.StateDelete, deleter)
|
||||||
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})
|
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{sm.JobID, models.JobFinished})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,13 +38,13 @@ func resumeJobs() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Failed to reset all running jobs to pending, error: %v", err)
|
log.Warningf("Failed to reset all running jobs to pending, error: %v", err)
|
||||||
}
|
}
|
||||||
jobs, err := dao.GetRepJobByStatus(models.JobPending)
|
jobs, err := dao.GetRepJobByStatus(models.JobPending, models.JobRetrying)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, j := range jobs {
|
for _, j := range jobs {
|
||||||
log.Debugf("Rescheduling job: %d", j.ID)
|
log.Debugf("Resuming job: %d", j.ID)
|
||||||
job.Schedule(j.ID)
|
job.Schedule(j.ID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warningf("Failed to get pending jobs, error: %v", err)
|
log.Warningf("Failed to jobs to resume, error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user