Inject logger into the execution context of running job for logging

define logger interface
implement a logger base on common log package
add getting log API
inject logger to the job execution context
This commit is contained in:
Steven Zou 2018-03-22 18:29:39 +08:00
parent b001180d26
commit 579b70c5e4
17 changed files with 416 additions and 44 deletions

View File

@ -28,6 +28,9 @@ type Handler interface {
//HandleCheckStatusReq is used to handle the job service healthy status checking request. //HandleCheckStatusReq is used to handle the job service healthy status checking request.
HandleCheckStatusReq(w http.ResponseWriter, req *http.Request) HandleCheckStatusReq(w http.ResponseWriter, req *http.Request)
//HandleJobLogReq is used to handle the request of getting job logs
HandleJobLogReq(w http.ResponseWriter, req *http.Request)
} }
//DefaultHandler is the default request handler which implements the Handler interface. //DefaultHandler is the default request handler which implements the Handler interface.
@ -88,7 +91,13 @@ func (dh *DefaultHandler) HandleGetJobReq(w http.ResponseWriter, req *http.Reque
jobStats, err := dh.controller.GetJob(jobID) jobStats, err := dh.controller.GetJob(jobID)
if err != nil { if err != nil {
dh.handleError(w, http.StatusInternalServerError, errs.GetJobStatsError(err)) code := http.StatusInternalServerError
backErr := errs.GetJobStatsError(err)
if errs.IsObjectNotFoundError(err) {
code = http.StatusNotFound
backErr = err
}
dh.handleError(w, code, backErr)
return return
} }
@ -126,17 +135,35 @@ func (dh *DefaultHandler) HandleJobActionReq(w http.ResponseWriter, req *http.Re
switch jobActionReq.Action { switch jobActionReq.Action {
case opm.CtlCommandStop: case opm.CtlCommandStop:
if err := dh.controller.StopJob(jobID); err != nil { if err := dh.controller.StopJob(jobID); err != nil {
dh.handleError(w, http.StatusInternalServerError, errs.StopJobError(err)) code := http.StatusInternalServerError
backErr := errs.StopJobError(err)
if errs.IsObjectNotFoundError(err) {
code = http.StatusNotFound
backErr = err
}
dh.handleError(w, code, backErr)
return return
} }
case opm.CtlCommandCancel: case opm.CtlCommandCancel:
if err := dh.controller.CancelJob(jobID); err != nil { if err := dh.controller.CancelJob(jobID); err != nil {
dh.handleError(w, http.StatusInternalServerError, errs.CancelJobError(err)) code := http.StatusInternalServerError
backErr := errs.CancelJobError(err)
if errs.IsObjectNotFoundError(err) {
code = http.StatusNotFound
backErr = err
}
dh.handleError(w, code, backErr)
return return
} }
case opm.CtlCommandRetry: case opm.CtlCommandRetry:
if err := dh.controller.RetryJob(jobID); err != nil { if err := dh.controller.RetryJob(jobID); err != nil {
dh.handleError(w, http.StatusInternalServerError, errs.RetryJobError(err)) code := http.StatusInternalServerError
backErr := errs.RetryJobError(err)
if errs.IsObjectNotFoundError(err) {
code = http.StatusNotFound
backErr = err
}
dh.handleError(w, code, backErr)
return return
} }
default: default:
@ -168,6 +195,31 @@ func (dh *DefaultHandler) HandleCheckStatusReq(w http.ResponseWriter, req *http.
w.Write(data) w.Write(data)
} }
//HandleJobLogReq is implementation of method defined in interface 'Handler'
func (dh *DefaultHandler) HandleJobLogReq(w http.ResponseWriter, req *http.Request) {
if !dh.preCheck(w) {
return
}
vars := mux.Vars(req)
jobID := vars["job_id"]
logData, err := dh.controller.GetJobLogData(jobID)
if err != nil {
code := http.StatusInternalServerError
backErr := errs.GetJobLogError(err)
if errs.IsObjectNotFoundError(err) {
code = http.StatusNotFound
backErr = err
}
dh.handleError(w, code, backErr)
return
}
w.WriteHeader(http.StatusOK)
w.Write(logData)
}
func (dh *DefaultHandler) handleJSONData(w http.ResponseWriter, object interface{}) ([]byte, bool) { func (dh *DefaultHandler) handleJSONData(w http.ResponseWriter, object interface{}) ([]byte, bool) {
data, err := json.Marshal(object) data, err := json.Marshal(object)
if err != nil { if err != nil {

View File

@ -54,5 +54,6 @@ func (br *BaseRouter) registerRoutes() {
subRouter.HandleFunc("/jobs", br.handler.HandleLaunchJobReq).Methods(http.MethodPost) subRouter.HandleFunc("/jobs", br.handler.HandleLaunchJobReq).Methods(http.MethodPost)
subRouter.HandleFunc("/jobs/{job_id}", br.handler.HandleGetJobReq).Methods(http.MethodGet) subRouter.HandleFunc("/jobs/{job_id}", br.handler.HandleGetJobReq).Methods(http.MethodGet)
subRouter.HandleFunc("/jobs/{job_id}", br.handler.HandleJobActionReq).Methods(http.MethodPost) subRouter.HandleFunc("/jobs/{job_id}", br.handler.HandleJobActionReq).Methods(http.MethodPost)
subRouter.HandleFunc("/jobs/{job_id}/log", br.handler.HandleJobLogReq).Methods(http.MethodGet)
subRouter.HandleFunc("/stats", br.handler.HandleCheckStatusReq).Methods(http.MethodGet) subRouter.HandleFunc("/stats", br.handler.HandleCheckStatusReq).Methods(http.MethodGet)
} }

View File

@ -20,5 +20,9 @@ worker_pool:
host: "10.160.178.186" host: "10.160.178.186"
port: 6379 port: 6379
namespace: "harbor_job_service" namespace: "harbor_job_service"
#Logger for job
#Logger for job
logger:
path: "/Users/szou/tmp/job_logs"
level: "INFO"

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"strconv" "strconv"
"strings"
"github.com/vmware/harbor/src/jobservice_v2/utils" "github.com/vmware/harbor/src/jobservice_v2/utils"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@ -23,6 +24,8 @@ const (
jobServiceRedisHost = "JOB_SERVICE_POOL_REDIS_HOST" jobServiceRedisHost = "JOB_SERVICE_POOL_REDIS_HOST"
jobServiceRedisPort = "JOB_SERVICE_POOL_REDIS_PORT" jobServiceRedisPort = "JOB_SERVICE_POOL_REDIS_PORT"
jobServiceRedisNamespace = "JOB_SERVICE_POOL_REDIS_NAMESPACE" jobServiceRedisNamespace = "JOB_SERVICE_POOL_REDIS_NAMESPACE"
jobServiceLoggerBasePath = "JOB_SERVICE_LOGGER_BASE_PATH"
jobServiceLoggerLevel = "JOB_SERVICE_LOGGER_LEVEL"
//JobServiceProtocolHTTPS points to the 'https' protocol //JobServiceProtocolHTTPS points to the 'https' protocol
JobServiceProtocolHTTPS = "https" JobServiceProtocolHTTPS = "https"
@ -33,6 +36,9 @@ const (
JobServicePoolBackendRedis = "redis" JobServicePoolBackendRedis = "redis"
) )
//DefaultConfig is the default configuration reference
var DefaultConfig = &Configuration{}
//Configuration loads and keeps the related configuration items of job service. //Configuration loads and keeps the related configuration items of job service.
type Configuration struct { type Configuration struct {
//Protocol server listening on: https/http //Protocol server listening on: https/http
@ -46,22 +52,25 @@ type Configuration struct {
//Configurations of worker pool //Configurations of worker pool
PoolConfig *PoolConfig `yaml:"worker_pool,omitempty"` PoolConfig *PoolConfig `yaml:"worker_pool,omitempty"`
//Logger configurations
LoggerConfig *LoggerConfig `yaml:"logger,omitempty"`
} }
//HTTPSConfig keep additional configurations when using https protocol //HTTPSConfig keeps additional configurations when using https protocol
type HTTPSConfig struct { type HTTPSConfig struct {
Cert string `yaml:"cert"` Cert string `yaml:"cert"`
Key string `yaml:"key"` Key string `yaml:"key"`
} }
//RedisPoolConfig keep redis pool info. //RedisPoolConfig keeps redis pool info.
type RedisPoolConfig struct { type RedisPoolConfig struct {
Host string `yaml:"host"` Host string `yaml:"host"`
Port uint `yaml:"port"` Port uint `yaml:"port"`
Namespace string `yaml:"namespace"` Namespace string `yaml:"namespace"`
} }
//PoolConfig keep worker pool configurations. //PoolConfig keeps worker pool configurations.
type PoolConfig struct { type PoolConfig struct {
//0 means unlimited //0 means unlimited
WorkerCount uint `yaml:"workers"` WorkerCount uint `yaml:"workers"`
@ -69,6 +78,12 @@ type PoolConfig struct {
RedisPoolCfg *RedisPoolConfig `yaml:"redis_pool,omitempty"` RedisPoolCfg *RedisPoolConfig `yaml:"redis_pool,omitempty"`
} }
//LoggerConfig keeps logger configurations.
type LoggerConfig struct {
BasePath string `yaml:"path"`
LogLevel string `yaml:"level"`
}
//Load the configuration options from the specified yaml file. //Load the configuration options from the specified yaml file.
//If the yaml file is specified and existing, load configurations from yaml file first; //If the yaml file is specified and existing, load configurations from yaml file first;
//If detecting env variables is specified, load configurations from env variables; //If detecting env variables is specified, load configurations from env variables;
@ -83,7 +98,6 @@ func (c *Configuration) Load(yamlFilePath string, detectEnv bool) error {
if err != nil { if err != nil {
return err return err
} }
if err = yaml.Unmarshal(data, c); err != nil { if err = yaml.Unmarshal(data, c); err != nil {
return err return err
} }
@ -98,6 +112,24 @@ func (c *Configuration) Load(yamlFilePath string, detectEnv bool) error {
return c.validate() return c.validate()
} }
//GetLogBasePath returns the log base path config
func GetLogBasePath() string {
if DefaultConfig.LoggerConfig != nil {
return DefaultConfig.LoggerConfig.BasePath
}
return ""
}
//GetLogLevel returns the log level
func GetLogLevel() string {
if DefaultConfig.LoggerConfig != nil {
return DefaultConfig.LoggerConfig.LogLevel
}
return ""
}
//Load env variables //Load env variables
func (c *Configuration) loadEnvs() { func (c *Configuration) loadEnvs() {
prot := utils.ReadEnv(jobServiceProtocol) prot := utils.ReadEnv(jobServiceProtocol)
@ -183,6 +215,16 @@ func (c *Configuration) loadEnvs() {
} }
} }
//logger
loggerPath := utils.ReadEnv(jobServiceLoggerBasePath)
if !utils.IsEmptyStr(loggerPath) {
c.LoggerConfig.BasePath = loggerPath
}
loggerLevel := utils.ReadEnv(jobServiceLoggerLevel)
if !utils.IsEmptyStr(loggerLevel) {
c.LoggerConfig.LogLevel = loggerLevel
}
} }
//Check if the configurations are valid settings. //Check if the configurations are valid settings.
@ -236,5 +278,18 @@ func (c *Configuration) validate() error {
} }
} }
if c.LoggerConfig == nil {
return errors.New("missing logger config")
}
if !utils.DirExists(c.LoggerConfig.BasePath) {
return errors.New("logger path should be an existing dir")
}
validLevels := "DEBUG,INFO,WARNING,ERROR,FATAL"
if !strings.Contains(validLevels, c.LoggerConfig.LogLevel) {
return fmt.Errorf("logger level can only be one of: %s", validLevels)
}
return nil //valid return nil //valid
} }

View File

@ -3,8 +3,11 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"github.com/robfig/cron" "github.com/robfig/cron"
"github.com/vmware/harbor/src/jobservice_v2/config"
"github.com/vmware/harbor/src/jobservice_v2/errs"
"github.com/vmware/harbor/src/jobservice_v2/job" "github.com/vmware/harbor/src/jobservice_v2/job"
"github.com/vmware/harbor/src/jobservice_v2/models" "github.com/vmware/harbor/src/jobservice_v2/models"
"github.com/vmware/harbor/src/jobservice_v2/pool" "github.com/vmware/harbor/src/jobservice_v2/pool"
@ -118,6 +121,25 @@ func (c *Controller) RetryJob(jobID string) error {
return c.backendPool.RetryJob(jobID) return c.backendPool.RetryJob(jobID)
} }
//GetJobLogData is used to return the log text data for the specified job if exists
func (c *Controller) GetJobLogData(jobID string) ([]byte, error) {
if utils.IsEmptyStr(jobID) {
return nil, errors.New("empty job ID")
}
logPath := fmt.Sprintf("%s/%s.log", config.GetLogBasePath(), jobID)
if !utils.FileExists(logPath) {
return nil, errs.NoObjectFoundError(fmt.Sprintf("%s.log", jobID))
}
logData, err := ioutil.ReadFile(logPath)
if err != nil {
return nil, err
}
return logData, nil
}
//CheckStatus is implementation of same method in core interface. //CheckStatus is implementation of same method in core interface.
func (c *Controller) CheckStatus() (models.JobPoolStats, error) { func (c *Controller) CheckStatus() (models.JobPoolStats, error) {
return c.backendPool.Stats() return c.backendPool.Stats()

View File

@ -53,4 +53,7 @@ type Interface interface {
//CheckStatus is used to handle the job service healthy status checking request. //CheckStatus is used to handle the job service healthy status checking request.
CheckStatus() (models.JobPoolStats, error) CheckStatus() (models.JobPoolStats, error)
//GetJobLogData is used to return the log text data for the specified job if exists
GetJobLogData(jobID string) ([]byte, error)
} }

View File

@ -2,7 +2,11 @@
package env package env
import "context" import (
"context"
"github.com/vmware/harbor/src/jobservice_v2/logger"
)
//JobContext is combination of BaseContext and other job specified resources. //JobContext is combination of BaseContext and other job specified resources.
//JobContext will be the real execution context for one job. //JobContext will be the real execution context for one job.
@ -45,6 +49,9 @@ type JobContext interface {
// op command if have // op command if have
// flag to indicate if have command // flag to indicate if have command
OPCommand() (string, bool) OPCommand() (string, bool)
//Return the logger
GetLogger() logger.Interface
} }
//JobData defines job context dependencies. //JobData defines job context dependencies.

View File

@ -28,10 +28,14 @@ const (
StopJobErrorCode StopJobErrorCode
//CancelJobErrorCode is code for the error of cancelling job //CancelJobErrorCode is code for the error of cancelling job
CancelJobErrorCode CancelJobErrorCode
//RetryJobErrorCode is code for the error of retry job //RetryJobErrorCode is code for the error of retrying job
RetryJobErrorCode RetryJobErrorCode
//UnknownActionNameErrorCode is code for the case of unknown action name //UnknownActionNameErrorCode is code for the case of unknown action name
UnknownActionNameErrorCode UnknownActionNameErrorCode
//GetJobLogErrorCode is code for the error of getting job log
GetJobLogErrorCode
//NoObjectFoundErrorCode is code for the error of no object found
NoObjectFoundErrorCode
) )
//baseError ... //baseError ...
@ -109,6 +113,11 @@ func UnknownActionNameError(err error) error {
return New(UnknownActionNameErrorCode, "Unknown job action name", err.Error()) return New(UnknownActionNameErrorCode, "Unknown job action name", err.Error())
} }
//GetJobLogError is error for the case of getting job log failed
func GetJobLogError(err error) error {
return New(GetJobLogErrorCode, "Failed to get the job log", err.Error())
}
//jobStoppedError is designed for the case of stopping job. //jobStoppedError is designed for the case of stopping job.
type jobStoppedError struct { type jobStoppedError struct {
baseError baseError
@ -139,6 +148,22 @@ func JobCancelledError() error {
} }
} }
//objectNotFound is designed for the case of no object found
type objectNotFoundError struct {
baseError
}
//NoObjectFoundError is error wrapper for the case of no object found
func NoObjectFoundError(object string) error {
return objectNotFoundError{
baseError{
Code: NoObjectFoundErrorCode,
Err: "object is not found",
Description: object,
},
}
}
//IsJobStoppedError return true if the error is jobStoppedError //IsJobStoppedError return true if the error is jobStoppedError
func IsJobStoppedError(err error) bool { func IsJobStoppedError(err error) bool {
_, ok := err.(jobStoppedError) _, ok := err.(jobStoppedError)
@ -150,3 +175,9 @@ func IsJobCancelledError(err error) bool {
_, ok := err.(jobCancelledError) _, ok := err.(jobCancelledError)
return ok return ok
} }
//IsObjectNotFoundError return true if the error is objectNotFoundError
func IsObjectNotFoundError(err error) bool {
_, ok := err.(objectNotFoundError)
return ok
}

View File

@ -1 +0,0 @@
subjectAltName = IP:10.4.142.42

View File

@ -5,11 +5,14 @@ package impl
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"reflect" "reflect"
hlog "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/jobservice_v2/config"
"github.com/vmware/harbor/src/jobservice_v2/env" "github.com/vmware/harbor/src/jobservice_v2/env"
"github.com/vmware/harbor/src/jobservice_v2/job" "github.com/vmware/harbor/src/jobservice_v2/job"
jlogger "github.com/vmware/harbor/src/jobservice_v2/job/impl/logger"
"github.com/vmware/harbor/src/jobservice_v2/logger"
) )
//Context ... //Context ...
@ -18,7 +21,7 @@ type Context struct {
sysContext context.Context sysContext context.Context
//Logger for job //Logger for job
logger *hlog.Logger logger logger.Interface
//op command func //op command func
opCommandFunc job.CheckOPCmdFunc opCommandFunc job.CheckOPCmdFunc
@ -46,7 +49,13 @@ func (c *Context) Build(dep env.JobData) (env.JobContext, error) {
sysContext: c.sysContext, sysContext: c.sysContext,
} }
//TODO:Init logger here //Init logger here
logPath := fmt.Sprintf("%s/%s.log", config.GetLogBasePath(), dep.ID)
jContext.logger = jlogger.New(logPath, config.GetLogLevel())
if jContext.logger == nil {
return nil, errors.New("failed to initialize job logger")
}
if opCommandFunc, ok := dep.ExtraData["opCommandFunc"]; ok { if opCommandFunc, ok := dep.ExtraData["opCommandFunc"]; ok {
if reflect.TypeOf(opCommandFunc).Kind() == reflect.Func { if reflect.TypeOf(opCommandFunc).Kind() == reflect.Func {
if funcRef, ok := opCommandFunc.(job.CheckOPCmdFunc); ok { if funcRef, ok := opCommandFunc.(job.CheckOPCmdFunc); ok {
@ -54,6 +63,9 @@ func (c *Context) Build(dep env.JobData) (env.JobContext, error) {
} }
} }
} }
if jContext.opCommandFunc == nil {
return nil, errors.New("failed to inject opCommandFunc")
}
if checkInFunc, ok := dep.ExtraData["checkInFunc"]; ok { if checkInFunc, ok := dep.ExtraData["checkInFunc"]; ok {
if reflect.TypeOf(checkInFunc).Kind() == reflect.Func { if reflect.TypeOf(checkInFunc).Kind() == reflect.Func {
@ -63,6 +75,10 @@ func (c *Context) Build(dep env.JobData) (env.JobContext, error) {
} }
} }
if jContext.checkInFunc == nil {
return nil, errors.New("failed to inject checkInFunc")
}
return jContext, nil return jContext, nil
} }
@ -95,3 +111,8 @@ func (c *Context) OPCommand() (string, bool) {
return "", false return "", false
} }
//GetLogger returns the logger
func (c *Context) GetLogger() logger.Interface {
return c.logger
}

View File

@ -0,0 +1,101 @@
package logger
import (
"os"
"strings"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/jobservice_v2/logger"
)
//JobLogger is an implementation of logger.Interface.
//It used in the job to output logs to the logfile.
type JobLogger struct {
backendLogger *log.Logger
}
//New logger
//nil might be returned
func New(logPath string, level string) logger.Interface {
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil
}
logLevel := parseLevel(level)
backendLogger := log.New(f, log.NewTextFormatter(), logLevel)
return &JobLogger{
backendLogger: backendLogger,
}
}
//Debug ...
func (jl *JobLogger) Debug(v ...interface{}) {
jl.backendLogger.Debug(v...)
}
//Debugf with format
func (jl *JobLogger) Debugf(format string, v ...interface{}) {
jl.backendLogger.Debugf(format, v...)
}
//Info ...
func (jl *JobLogger) Info(v ...interface{}) {
jl.backendLogger.Info(v...)
}
//Infof with format
func (jl *JobLogger) Infof(format string, v ...interface{}) {
jl.backendLogger.Infof(format, v...)
}
//Warning ...
func (jl *JobLogger) Warning(v ...interface{}) {
jl.backendLogger.Warning(v...)
}
//Warningf with format
func (jl *JobLogger) Warningf(format string, v ...interface{}) {
jl.backendLogger.Warningf(format, v...)
}
//Error ...
func (jl *JobLogger) Error(v ...interface{}) {
jl.backendLogger.Error(v...)
}
//Errorf with format
func (jl *JobLogger) Errorf(format string, v ...interface{}) {
jl.backendLogger.Errorf(format, v...)
}
//Fatal error
func (jl *JobLogger) Fatal(v ...interface{}) {
jl.backendLogger.Fatal(v...)
}
//Fatalf error
func (jl *JobLogger) Fatalf(format string, v ...interface{}) {
jl.backendLogger.Fatalf(format, v...)
}
func parseLevel(lvl string) log.Level {
var level = log.WarningLevel
switch strings.ToLower(lvl) {
case "debug":
level = log.DebugLevel
case "info":
level = log.InfoLevel
case "warning":
level = log.WarningLevel
case "error":
level = log.ErrorLevel
case "fatal":
level = log.FatalLevel
default:
}
return level
}

View File

@ -47,42 +47,47 @@ func (rj *ReplicationJob) Validate(params map[string]interface{}) error {
//Run the replication logic here. //Run the replication logic here.
func (rj *ReplicationJob) Run(ctx env.JobContext, params map[string]interface{}) error { func (rj *ReplicationJob) Run(ctx env.JobContext, params map[string]interface{}) error {
logger := ctx.GetLogger()
defer func() { defer func() {
logger.Info("I'm finished, exit!")
fmt.Println("I'm finished, exit!") fmt.Println("I'm finished, exit!")
}() }()
fmt.Println("=======Replication job running=======") logger.Info("=======Replication job running=======")
fmt.Printf("params: %#v\n", params) logger.Infof("params: %#v\n", params)
fmt.Printf("context: %#v\n", ctx) logger.Infof("context: %#v\n", ctx)
/*if 1 != 0 { /*if 1 != 0 {
return errors.New("I suicide") return errors.New("I suicide")
}*/ }*/
fmt.Println("check in 30%") logger.Info("check in 30%")
ctx.Checkin("30%") ctx.Checkin("30%")
time.Sleep(5 * time.Second) time.Sleep(2 * time.Second)
fmt.Println("check in 60%") logger.Warning("check in 60%")
ctx.Checkin("60%") ctx.Checkin("60%")
time.Sleep(5 * time.Second) time.Sleep(2 * time.Second)
fmt.Println("check in 100%") logger.Debug("check in 100%")
ctx.Checkin("100%") ctx.Checkin("100%")
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
//HOLD ON FOR A WHILE //HOLD ON FOR A WHILE
fmt.Println("Holding for 20 sec") logger.Error("Holding for 20 sec")
<-time.After(20 * time.Second) <-time.After(10 * time.Second)
fmt.Println("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 {
fmt.Printf("cmd=%s\n", cmd) logger.Infof("cmd=%s\n", cmd)
if cmd == opm.CtlCommandCancel { if cmd == opm.CtlCommandCancel {
fmt.Println("exit for receiving cancel signal") logger.Info("exit for receiving cancel signal")
return errs.JobCancelledError() return errs.JobCancelledError()
} }
fmt.Println("exit for receiving stop signal") logger.Info("exit for receiving stop signal")
return errs.JobStoppedError() return errs.JobStoppedError()
} }
fmt.Println("I'm here")
return nil return nil
} }

View File

@ -0,0 +1,36 @@
// Copyright 2018 The Harbor Authors. All rights reserved.
package logger
//Interface for logger.
type Interface interface {
//For debuging
Debug(v ...interface{})
//For debuging with format
Debugf(format string, v ...interface{})
//For logging info
Info(v ...interface{})
//For logging info with format
Infof(format string, v ...interface{})
//For warning
Warning(v ...interface{})
//For warning with format
Warningf(format string, v ...interface{})
//For logging error
Error(v ...interface{})
//For logging error with format
Errorf(format string, v ...interface{})
//For fatal error
Fatal(v ...interface{})
//For fatal error with error
Fatalf(format string, v ...interface{})
}

View File

@ -12,6 +12,8 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/vmware/harbor/src/jobservice_v2/errs"
"github.com/vmware/harbor/src/jobservice_v2/period" "github.com/vmware/harbor/src/jobservice_v2/period"
"github.com/robfig/cron" "github.com/robfig/cron"
@ -543,7 +545,7 @@ func (rjs *RedisJobStatsManager) getJobStats(jobID string) (models.JobStats, err
} }
if vals == nil || len(vals) == 0 { if vals == nil || len(vals) == 0 {
return models.JobStats{}, fmt.Errorf("job '%s' is not found", jobID) return models.JobStats{}, errs.NoObjectFoundError(fmt.Sprintf("job '%s'", jobID))
} }
res := models.JobStats{ res := models.JobStats{

View File

@ -30,16 +30,29 @@ func NewRedisJob(j interface{}, ctx *env.Context, statsManager opm.JobStatsManag
//Run the job //Run the job
func (rj *RedisJob) Run(j *work.Job) error { func (rj *RedisJob) Run(j *work.Job) error {
var cancelled = false var (
cancelled = false
buildContextFailed = false
runningJob job.Interface
err error
execContext env.JobContext
)
execContext, err := rj.buildContext(j) execContext, err = rj.buildContext(j)
if err != nil { if err != nil {
return err buildContextFailed = true
goto FAILED //no need to retry
} }
runningJob := Wrap(rj.job) //Wrap job
runningJob = Wrap(rj.job)
defer func() { defer func() {
if rj.shouldDisableRetry(runningJob, j, cancelled) { if err == nil {
return //nothing need to do
}
if buildContextFailed || rj.shouldDisableRetry(runningJob, j, cancelled) {
j.Fails = 10000000000 //Make it big enough to avoid retrying j.Fails = 10000000000 //Make it big enough to avoid retrying
now := time.Now().Unix() now := time.Now().Unix()
go func() { go func() {
@ -75,6 +88,7 @@ func (rj *RedisJob) Run(j *work.Job) error {
return err //need to resume return err //need to resume
} }
FAILED:
rj.jobFailed(j.ID) rj.jobFailed(j.ID)
return err return err
} }

View File

@ -31,8 +31,7 @@ type Bootstrap struct{}
//Return error if meet any problems. //Return error if meet any problems.
func (bs *Bootstrap) LoadAndRun(configFile string, detectEnv bool) { func (bs *Bootstrap) LoadAndRun(configFile string, detectEnv bool) {
//Load configurations //Load configurations
cfg := config.Configuration{} if err := config.DefaultConfig.Load(configFile, detectEnv); err != nil {
if err := cfg.Load(configFile, detectEnv); err != nil {
log.Errorf("Failed to load configurations with error: %s\n", err) log.Errorf("Failed to load configurations with error: %s\n", err)
return return
} }
@ -58,16 +57,16 @@ func (bs *Bootstrap) LoadAndRun(configFile string, detectEnv bool) {
//Start the pool //Start the pool
var backendPool pool.Interface var backendPool pool.Interface
if cfg.PoolConfig.Backend == config.JobServicePoolBackendRedis { if config.DefaultConfig.PoolConfig.Backend == config.JobServicePoolBackendRedis {
backendPool = bs.loadAndRunRedisWorkerPool(rootContext, cfg) backendPool = bs.loadAndRunRedisWorkerPool(rootContext, config.DefaultConfig)
} }
//Initialize controller //Initialize controller
ctl := core.NewController(backendPool) ctl := core.NewController(backendPool)
//Start the API server //Start the API server
apiServer := bs.loadAndRunAPIServer(rootContext, cfg, ctl) apiServer := bs.loadAndRunAPIServer(rootContext, config.DefaultConfig, ctl)
log.Infof("Server is started at %s:%d with %s", "", cfg.Port, cfg.Protocol) log.Infof("Server is started at %s:%d with %s", "", config.DefaultConfig.Port, config.DefaultConfig.Protocol)
//Block here //Block here
sig := make(chan os.Signal, 1) sig := make(chan os.Signal, 1)
@ -107,7 +106,7 @@ func (bs *Bootstrap) LoadAndRun(configFile string, detectEnv bool) {
} }
//Load and run the API server. //Load and run the API server.
func (bs *Bootstrap) loadAndRunAPIServer(ctx *env.Context, cfg config.Configuration, ctl *core.Controller) *api.Server { func (bs *Bootstrap) loadAndRunAPIServer(ctx *env.Context, cfg *config.Configuration, ctl *core.Controller) *api.Server {
//Initialized API server //Initialized API server
handler := api.NewDefaultHandler(ctl) handler := api.NewDefaultHandler(ctl)
router := api.NewBaseRouter(handler) router := api.NewBaseRouter(handler)
@ -128,7 +127,7 @@ func (bs *Bootstrap) loadAndRunAPIServer(ctx *env.Context, cfg config.Configurat
} }
//Load and run the worker pool //Load and run the worker pool
func (bs *Bootstrap) loadAndRunRedisWorkerPool(ctx *env.Context, cfg config.Configuration) pool.Interface { func (bs *Bootstrap) loadAndRunRedisWorkerPool(ctx *env.Context, cfg *config.Configuration) pool.Interface {
redisPoolCfg := pool.RedisPoolConfig{ redisPoolCfg := pool.RedisPoolConfig{
RedisHost: cfg.PoolConfig.RedisPoolCfg.Host, RedisHost: cfg.PoolConfig.RedisPoolCfg.Host,
RedisPort: cfg.PoolConfig.RedisPoolCfg.Port, RedisPort: cfg.PoolConfig.RedisPoolCfg.Port,

View File

@ -24,14 +24,34 @@ func ReadEnv(key string) string {
//FileExists check if the specified exists. //FileExists check if the specified exists.
func FileExists(file string) bool { func FileExists(file string) bool {
if !IsEmptyStr(file) { if !IsEmptyStr(file) {
if _, err := os.Stat(file); err == nil { _, err := os.Stat(file)
if err == nil {
return true return true
} }
if os.IsNotExist(err) {
return false
}
return true
} }
return false return false
} }
//DirExists check if the specified dir exists
func DirExists(path string) bool {
if IsEmptyStr(path) {
return false
}
f, err := os.Stat(path)
if err != nil {
return false
}
return f.IsDir()
}
//IsValidPort check if port is valid. //IsValidPort check if port is valid.
func IsValidPort(port uint) bool { func IsValidPort(port uint) bool {
return port != 0 && port < 65536 return port != 0 && port < 65536