mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-30 06:03:45 +01:00
Fix issue of stopping periodic job
improve op command by using cache return 404 if no job found to stop
This commit is contained in:
parent
e4952b7a63
commit
6c69a8cd05
@ -55,6 +55,7 @@ func (dj *DemoJob) Run(ctx env.JobContext, params map[string]interface{}) error
|
|||||||
logger.Info("I'm finished, exit!")
|
logger.Info("I'm finished, exit!")
|
||||||
fmt.Println("I'm finished, exit!")
|
fmt.Println("I'm finished, exit!")
|
||||||
}()
|
}()
|
||||||
|
fmt.Println("I'm running")
|
||||||
logger.Info("=======Replication job running=======")
|
logger.Info("=======Replication job running=======")
|
||||||
logger.Infof("params: %#v\n", params)
|
logger.Infof("params: %#v\n", params)
|
||||||
logger.Infof("context: %#v\n", ctx)
|
logger.Infof("context: %#v\n", ctx)
|
||||||
@ -81,11 +82,12 @@ func (dj *DemoJob) Run(ctx env.JobContext, params map[string]interface{}) error
|
|||||||
|
|
||||||
//HOLD ON FOR A WHILE
|
//HOLD ON FOR A WHILE
|
||||||
logger.Error("Holding for 20 sec")
|
logger.Error("Holding for 20 sec")
|
||||||
<-time.After(10 * time.Second)
|
<-time.After(15 * time.Second)
|
||||||
//logger.Fatal("I'm back, check if I'm stopped/cancelled")
|
//logger.Fatal("I'm back, check if I'm stopped/cancelled")
|
||||||
|
|
||||||
if cmd, ok := ctx.OPCommand(); ok {
|
if cmd, ok := ctx.OPCommand(); ok {
|
||||||
logger.Infof("cmd=%s\n", cmd)
|
logger.Infof("cmd=%s\n", cmd)
|
||||||
|
fmt.Printf("Receive OP command: %s\n", cmd)
|
||||||
if cmd == opm.CtlCommandCancel {
|
if cmd == opm.CtlCommandCancel {
|
||||||
logger.Info("exit for receiving cancel signal")
|
logger.Info("exit for receiving cancel signal")
|
||||||
return errs.JobCancelledError()
|
return errs.JobCancelledError()
|
||||||
@ -95,7 +97,7 @@ func (dj *DemoJob) Run(ctx env.JobContext, params map[string]interface{}) error
|
|||||||
return errs.JobStoppedError()
|
return errs.JobStoppedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("I'm here")
|
fmt.Println("I'm close to end")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
166
src/jobservice/opm/op_commands.go
Normal file
166
src/jobservice/opm/op_commands.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// Copyright 2018 The Harbor Authors. All rights reserved.
|
||||||
|
|
||||||
|
package opm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/logger"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/models"
|
||||||
|
"github.com/vmware/harbor/src/jobservice/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
commandValidTime = 5 * time.Minute
|
||||||
|
commandSweepTickerTime = 1 * time.Hour
|
||||||
|
//EventFireCommand for firing command event
|
||||||
|
EventFireCommand = "fire_command"
|
||||||
|
)
|
||||||
|
|
||||||
|
type oPCommand struct {
|
||||||
|
command string
|
||||||
|
fireTime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
//oPCommands maintain commands list
|
||||||
|
type oPCommands struct {
|
||||||
|
lock *sync.RWMutex
|
||||||
|
commands map[string]*oPCommand
|
||||||
|
context context.Context
|
||||||
|
redisPool *redis.Pool
|
||||||
|
namespace string
|
||||||
|
stopChan chan struct{}
|
||||||
|
doneChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//newOPCommands is constructor of OPCommands
|
||||||
|
func newOPCommands(ctx context.Context, ns string, redisPool *redis.Pool) *oPCommands {
|
||||||
|
return &oPCommands{
|
||||||
|
lock: new(sync.RWMutex),
|
||||||
|
commands: make(map[string]*oPCommand),
|
||||||
|
context: ctx,
|
||||||
|
redisPool: redisPool,
|
||||||
|
namespace: ns,
|
||||||
|
stopChan: make(chan struct{}, 1),
|
||||||
|
doneChan: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Start the command sweeper
|
||||||
|
func (opc *oPCommands) Start() {
|
||||||
|
go opc.loop()
|
||||||
|
logger.Info("OP commands sweeper is started")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Stop the command sweeper
|
||||||
|
func (opc *oPCommands) Stop() {
|
||||||
|
opc.stopChan <- struct{}{}
|
||||||
|
<-opc.doneChan
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fire command
|
||||||
|
func (opc *oPCommands) Fire(jobID string, command string) error {
|
||||||
|
if utils.IsEmptyStr(jobID) {
|
||||||
|
return errors.New("empty job ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if command != CtlCommandStop && command != CtlCommandCancel {
|
||||||
|
return fmt.Errorf("Unsupported command %s", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
notification := &models.Message{
|
||||||
|
Event: EventFireCommand,
|
||||||
|
Data: []string{jobID, command},
|
||||||
|
}
|
||||||
|
|
||||||
|
rawJSON, err := json.Marshal(notification)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := opc.redisPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
_, err = conn.Do("PUBLISH", utils.KeyPeriodicNotification(opc.namespace), rawJSON)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Push command into the list
|
||||||
|
func (opc *oPCommands) Push(jobID string, command string) error {
|
||||||
|
if utils.IsEmptyStr(jobID) {
|
||||||
|
return errors.New("empty job ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if command != CtlCommandStop && command != CtlCommandCancel {
|
||||||
|
return fmt.Errorf("Unsupported command %s", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
opc.lock.Lock()
|
||||||
|
defer opc.lock.Unlock()
|
||||||
|
|
||||||
|
opc.commands[jobID] = &oPCommand{
|
||||||
|
command: command,
|
||||||
|
fireTime: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Pop out the command if existing
|
||||||
|
func (opc *oPCommands) Pop(jobID string) (string, bool) {
|
||||||
|
if utils.IsEmptyStr(jobID) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
opc.lock.RLock()
|
||||||
|
defer opc.lock.RUnlock()
|
||||||
|
|
||||||
|
c, ok := opc.commands[jobID]
|
||||||
|
if ok {
|
||||||
|
if time.Unix(c.fireTime, 0).Add(commandValidTime).After(time.Now()) {
|
||||||
|
delete(opc.commands, jobID)
|
||||||
|
return c.command, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opc *oPCommands) loop() {
|
||||||
|
defer func() {
|
||||||
|
logger.Info("OP commands is stopped")
|
||||||
|
opc.doneChan <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
tk := time.NewTicker(commandSweepTickerTime)
|
||||||
|
defer tk.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tk.C:
|
||||||
|
opc.sweepCommands()
|
||||||
|
case <-opc.context.Done():
|
||||||
|
return
|
||||||
|
case <-opc.stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opc *oPCommands) sweepCommands() {
|
||||||
|
opc.lock.Lock()
|
||||||
|
defer opc.lock.Unlock()
|
||||||
|
|
||||||
|
for k, v := range opc.commands {
|
||||||
|
if time.Unix(v.fireTime, 0).Add(commandValidTime).After(time.Now()) {
|
||||||
|
delete(opc.commands, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,8 @@ type RedisJobStatsManager struct {
|
|||||||
doneChan chan struct{}
|
doneChan chan struct{}
|
||||||
processChan chan *queueItem
|
processChan chan *queueItem
|
||||||
isRunning *atomic.Value
|
isRunning *atomic.Value
|
||||||
hookStore *HookStore //cache the hook here to avoid requesting backend
|
hookStore *HookStore //cache the hook here to avoid requesting backend
|
||||||
|
opCommands *oPCommands //maintain the OP commands
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewRedisJobStatsManager is constructor of RedisJobStatsManager
|
//NewRedisJobStatsManager is constructor of RedisJobStatsManager
|
||||||
@ -74,6 +75,7 @@ func NewRedisJobStatsManager(ctx context.Context, namespace string, redisPool *r
|
|||||||
processChan: make(chan *queueItem, processBufferSize),
|
processChan: make(chan *queueItem, processBufferSize),
|
||||||
hookStore: NewHookStore(),
|
hookStore: NewHookStore(),
|
||||||
isRunning: isRunning,
|
isRunning: isRunning,
|
||||||
|
opCommands: newOPCommands(ctx, namespace, redisPool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +85,7 @@ func (rjs *RedisJobStatsManager) Start() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
go rjs.loop()
|
go rjs.loop()
|
||||||
|
rjs.opCommands.Start()
|
||||||
rjs.isRunning.Store(true)
|
rjs.isRunning.Store(true)
|
||||||
|
|
||||||
logger.Info("Redis job stats manager is started")
|
logger.Info("Redis job stats manager is started")
|
||||||
@ -97,6 +100,8 @@ func (rjs *RedisJobStatsManager) Shutdown() {
|
|||||||
if !(rjs.isRunning.Load().(bool)) {
|
if !(rjs.isRunning.Load().(bool)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rjs.opCommands.Stop()
|
||||||
rjs.stopChan <- struct{}{}
|
rjs.stopChan <- struct{}{}
|
||||||
<-rjs.doneChan
|
<-rjs.doneChan
|
||||||
}
|
}
|
||||||
@ -213,7 +218,12 @@ func (rjs *RedisJobStatsManager) SendCommand(jobID string, command string) error
|
|||||||
return errors.New("unknown command")
|
return errors.New("unknown command")
|
||||||
}
|
}
|
||||||
|
|
||||||
return rjs.writeCtlCommand(jobID, command)
|
if err := rjs.opCommands.Fire(jobID, command); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Directly add to op commands maintaining list
|
||||||
|
return rjs.opCommands.Push(jobID, command)
|
||||||
}
|
}
|
||||||
|
|
||||||
//CheckIn mesage
|
//CheckIn mesage
|
||||||
@ -239,7 +249,12 @@ func (rjs *RedisJobStatsManager) CtlCommand(jobID string) (string, error) {
|
|||||||
return "", errors.New("empty job ID")
|
return "", errors.New("empty job ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
return rjs.getCrlCommand(jobID)
|
c, ok := rjs.opCommands.Pop(jobID)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("no OP command fired to job %s", jobID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//DieAt marks the failed jobs with the time they put into dead queue.
|
//DieAt marks the failed jobs with the time they put into dead queue.
|
||||||
@ -262,7 +277,7 @@ func (rjs *RedisJobStatsManager) RegisterHook(jobID string, hookURL string, isCa
|
|||||||
return errors.New("empty job ID")
|
return errors.New("empty job ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.IsEmptyStr(hookURL) {
|
if !utils.IsValidURL(hookURL) {
|
||||||
return errors.New("invalid hook url")
|
return errors.New("invalid hook url")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +317,7 @@ func (rjs *RedisJobStatsManager) submitStatusReportingItem(jobID string, status,
|
|||||||
if !ok {
|
if !ok {
|
||||||
//Retrieve from backend
|
//Retrieve from backend
|
||||||
hookURL, err = rjs.getHook(jobID)
|
hookURL, err = rjs.getHook(jobID)
|
||||||
if err != nil {
|
if err != nil || !utils.IsValidURL(hookURL) {
|
||||||
//logged and exit
|
//logged and exit
|
||||||
logger.Warningf("no status hook found for job %s\n, abandon status reporting", jobID)
|
logger.Warningf("no status hook found for job %s\n, abandon status reporting", jobID)
|
||||||
return
|
return
|
||||||
@ -328,45 +343,6 @@ func (rjs *RedisJobStatsManager) reportStatus(jobID string, hookURL, status, che
|
|||||||
return DefaultHookClient.ReportStatus(hookURL, reportingStatus)
|
return DefaultHookClient.ReportStatus(hookURL, reportingStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rjs *RedisJobStatsManager) getCrlCommand(jobID string) (string, error) {
|
|
||||||
conn := rjs.redisPool.Get()
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
key := utils.KeyJobCtlCommands(rjs.namespace, jobID)
|
|
||||||
cmd, err := redis.String(conn.Do("HGET", key, "command"))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
//try to DEL it after getting the command
|
|
||||||
//Ignore the error,leave it as dirty data
|
|
||||||
_, err = conn.Do("DEL", key)
|
|
||||||
if err != nil {
|
|
||||||
//only logged
|
|
||||||
logger.Errorf("del key %s failed with error: %s\n", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rjs *RedisJobStatsManager) writeCtlCommand(jobID string, command string) error {
|
|
||||||
conn := rjs.redisPool.Get()
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
key := utils.KeyJobCtlCommands(rjs.namespace, jobID)
|
|
||||||
args := make([]interface{}, 0, 5)
|
|
||||||
args = append(args, key, "command", command, "fire_time", time.Now().Unix())
|
|
||||||
if err := conn.Send("HMSET", args...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
expireTime := 24*60*60 + rand.Int63n(10)
|
|
||||||
if err := conn.Send("EXPIRE", key, expireTime); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rjs *RedisJobStatsManager) updateJobStatus(jobID string, status string) error {
|
func (rjs *RedisJobStatsManager) updateJobStatus(jobID string, status string) error {
|
||||||
conn := rjs.redisPool.Get()
|
conn := rjs.redisPool.Get()
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
@ -87,11 +87,6 @@ func TestCommand(t *testing.T) {
|
|||||||
t.Fatalf("expect '%s' but got '%s'", CtlCommandStop, cmd)
|
t.Fatalf("expect '%s' but got '%s'", CtlCommandStop, cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
key := utils.KeyJobCtlCommands(testingNamespace, "fake_job_ID")
|
|
||||||
if err := clear(key, redisPool.Get()); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDieAt(t *testing.T) {
|
func TestDieAt(t *testing.T) {
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/jobservice/errs"
|
||||||
|
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
|
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/garyburd/redigo/redis"
|
||||||
@ -156,6 +158,10 @@ func (rps *RedisPeriodicScheduler) UnSchedule(cronJobPolicyID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
score, err := rps.getScoreByID(cronJobPolicyID)
|
score, err := rps.getScoreByID(cronJobPolicyID)
|
||||||
|
if err == redis.ErrNil {
|
||||||
|
return errs.NoObjectFoundError(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,9 @@ func (ms *MessageServer) Start() error {
|
|||||||
dt, _ := json.Marshal(m.Data)
|
dt, _ := json.Marshal(m.Data)
|
||||||
json.Unmarshal(dt, hookObject)
|
json.Unmarshal(dt, hookObject)
|
||||||
converted = hookObject
|
converted = hookObject
|
||||||
|
case opm.EventFireCommand:
|
||||||
|
//no need to convert []string
|
||||||
|
converted = m.Data
|
||||||
}
|
}
|
||||||
res := callback.Call([]reflect.Value{reflect.ValueOf(converted)})
|
res := callback.Call([]reflect.Value{reflect.ValueOf(converted)})
|
||||||
e := res[0].Interface()
|
e := res[0].Interface()
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -141,6 +142,56 @@ func TestPublishHook(t *testing.T) {
|
|||||||
ms.Start()
|
ms.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPublishCommands(t *testing.T) {
|
||||||
|
ms, cancel := createMessageServer()
|
||||||
|
err := ms.Subscribe(opm.EventFireCommand, func(data interface{}) error {
|
||||||
|
cmds, ok := data.([]string)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expect fired command but got other thing")
|
||||||
|
return errors.New("expect fired command but got other thing")
|
||||||
|
}
|
||||||
|
if len(cmds) != 2 {
|
||||||
|
t.Fatalf("expect a array with 2 items but only got '%d' items", len(cmds))
|
||||||
|
return fmt.Errorf("expect a array with 2 items but only got '%d' items", len(cmds))
|
||||||
|
}
|
||||||
|
if cmds[1] != "stop" {
|
||||||
|
t.Fatalf("expect command 'stop' but got '%s'", cmds[1])
|
||||||
|
return fmt.Errorf("expect command 'stop' but got '%s'", cmds[1])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer cancel()
|
||||||
|
<-time.After(200 * time.Millisecond)
|
||||||
|
|
||||||
|
notification := &models.Message{
|
||||||
|
Event: opm.EventRegisterStatusHook,
|
||||||
|
Data: []string{"fake_job_ID", "stop"},
|
||||||
|
}
|
||||||
|
|
||||||
|
rawJSON, err := json.Marshal(notification)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := redisPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
err = conn.Send("PUBLISH", utils.KeyPeriodicNotification(tests.GiveMeTestNamespace()), rawJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//hold for a while
|
||||||
|
<-time.After(200 * time.Millisecond)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ms.Start()
|
||||||
|
}
|
||||||
|
|
||||||
func createMessageServer() (*MessageServer, context.CancelFunc) {
|
func createMessageServer() (*MessageServer, context.CancelFunc) {
|
||||||
ns := tests.GiveMeTestNamespace()
|
ns := tests.GiveMeTestNamespace()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
@ -123,6 +123,12 @@ func (gcwp *GoCraftWorkPool) Start() {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err = gcwp.messageServer.Subscribe(opm.EventFireCommand,
|
||||||
|
func(data interface{}) error {
|
||||||
|
return gcwp.handleOPCommandFiring(data)
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//Start message server
|
//Start message server
|
||||||
if err = gcwp.messageServer.Start(); err != nil {
|
if err = gcwp.messageServer.Start(); err != nil {
|
||||||
@ -323,8 +329,6 @@ func (gcwp *GoCraftWorkPool) Stats() (models.JobPoolStats, error) {
|
|||||||
return models.JobPoolStats{}, err
|
return models.JobPoolStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("hbs=%+#v\n", hbs[0])
|
|
||||||
|
|
||||||
//Find the heartbeat of this pool via pid
|
//Find the heartbeat of this pool via pid
|
||||||
stats := make([]*models.JobPoolStatsData, 0)
|
stats := make([]*models.JobPoolStatsData, 0)
|
||||||
for _, hb := range hbs {
|
for _, hb := range hbs {
|
||||||
@ -367,9 +371,14 @@ func (gcwp *GoCraftWorkPool) StopJob(jobID string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needSetStopStatus := false
|
||||||
|
|
||||||
switch theJob.Stats.JobKind {
|
switch theJob.Stats.JobKind {
|
||||||
case job.JobKindGeneric:
|
case job.JobKindGeneric:
|
||||||
//nothing need to do
|
//Only running job can be stopped
|
||||||
|
if theJob.Stats.Status != job.JobStatusRunning {
|
||||||
|
return fmt.Errorf("job '%s' is not a running job", jobID)
|
||||||
|
}
|
||||||
case job.JobKindScheduled:
|
case job.JobKindScheduled:
|
||||||
//we need to delete the scheduled job in the queue if it is not running yet
|
//we need to delete the scheduled job in the queue if it is not running yet
|
||||||
//otherwise, nothing need to do
|
//otherwise, nothing need to do
|
||||||
@ -377,6 +386,7 @@ func (gcwp *GoCraftWorkPool) StopJob(jobID string) error {
|
|||||||
if err := gcwp.client.DeleteScheduledJob(theJob.Stats.RunAt, jobID); err != nil {
|
if err := gcwp.client.DeleteScheduledJob(theJob.Stats.RunAt, jobID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
needSetStopStatus = true
|
||||||
}
|
}
|
||||||
case job.JobKindPeriodic:
|
case job.JobKindPeriodic:
|
||||||
//firstly delete the periodic job policy
|
//firstly delete the periodic job policy
|
||||||
@ -390,6 +400,8 @@ func (gcwp *GoCraftWorkPool) StopJob(jobID string) error {
|
|||||||
//only logged
|
//only logged
|
||||||
logger.Errorf("Expire the stats of job %s failed with error: %s\n", theJob.Stats.JobID, err)
|
logger.Errorf("Expire the stats of job %s failed with error: %s\n", theJob.Stats.JobID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needSetStopStatus = true
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -400,6 +412,13 @@ func (gcwp *GoCraftWorkPool) StopJob(jobID string) error {
|
|||||||
if err := gcwp.statsManager.SendCommand(jobID, opm.CtlCommandStop); err != nil {
|
if err := gcwp.statsManager.SendCommand(jobID, opm.CtlCommandStop); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
//The job running instance will set the status to 'stopped'
|
||||||
|
needSetStopStatus = false
|
||||||
|
}
|
||||||
|
|
||||||
|
//If needed, update the job status to 'stopped'
|
||||||
|
if needSetStopStatus {
|
||||||
|
gcwp.statsManager.SetJobStatus(jobID, job.JobStatusStopped)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -475,8 +494,8 @@ func (gcwp *GoCraftWorkPool) RegisterHook(jobID string, hookURL string) error {
|
|||||||
return errors.New("empty job ID")
|
return errors.New("empty job ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.IsEmptyStr(hookURL) {
|
if !utils.IsValidURL(hookURL) {
|
||||||
return errors.New("empty hook url")
|
return errors.New("invalid hook url")
|
||||||
}
|
}
|
||||||
|
|
||||||
return gcwp.statsManager.RegisterHook(jobID, hookURL, false)
|
return gcwp.statsManager.RegisterHook(jobID, hookURL, false)
|
||||||
@ -550,6 +569,24 @@ func (gcwp *GoCraftWorkPool) handleRegisterStatusHook(data interface{}) error {
|
|||||||
return gcwp.statsManager.RegisterHook(hook.JobID, hook.HookURL, true)
|
return gcwp.statsManager.RegisterHook(hook.JobID, hook.HookURL, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gcwp *GoCraftWorkPool) handleOPCommandFiring(data interface{}) error {
|
||||||
|
if data == nil {
|
||||||
|
return errors.New("nil data interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
commands, ok := data.([]interface{})
|
||||||
|
if !ok || len(commands) != 2 {
|
||||||
|
return errors.New("malformed op commands object")
|
||||||
|
}
|
||||||
|
jobID, ok := commands[0].(string)
|
||||||
|
command, ok := commands[1].(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("malformed op command info")
|
||||||
|
}
|
||||||
|
|
||||||
|
return gcwp.statsManager.SendCommand(jobID, command)
|
||||||
|
}
|
||||||
|
|
||||||
//log the job
|
//log the job
|
||||||
func (rpc *RedisPoolContext) logJob(job *work.Job, next work.NextMiddlewareFunc) error {
|
func (rpc *RedisPoolContext) logJob(job *work.Job, next work.NextMiddlewareFunc) error {
|
||||||
logger.Infof("Job incoming: %s:%s", job.Name, job.ID)
|
logger.Infof("Job incoming: %s:%s", job.Name, job.ID)
|
||||||
|
@ -5,6 +5,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -57,6 +58,19 @@ func IsValidPort(port uint) bool {
|
|||||||
return port != 0 && port < 65536
|
return port != 0 && port < 65536
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//IsValidURL validates if the url is well-formted
|
||||||
|
func IsValidURL(address string) bool {
|
||||||
|
if IsEmptyStr(address) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := url.Parse(address); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
//JobScore represents the data item with score in the redis db.
|
//JobScore represents the data item with score in the redis db.
|
||||||
type JobScore struct {
|
type JobScore struct {
|
||||||
JobBytes []byte
|
JobBytes []byte
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
package target
|
package target
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
@ -42,7 +44,7 @@ func (d *DefaultManager) GetTarget(id int64) (*models.RepTarget, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if target == nil {
|
if target == nil {
|
||||||
return nil, nil
|
return nil, fmt.Errorf("target '%d' does not exist", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// decrypt the password
|
// decrypt the password
|
||||||
|
Loading…
Reference in New Issue
Block a user