Add UT cases for new added code

Signed-off-by: Steven Zou <szou@vmware.com>
This commit is contained in:
Steven Zou 2018-11-30 14:27:47 +08:00
parent ccd486a0ad
commit 84d864607c
5 changed files with 80 additions and 18 deletions

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/goharbor/harbor/src/jobservice/errs"
"github.com/goharbor/harbor/src/jobservice/models" "github.com/goharbor/harbor/src/jobservice/models"
"github.com/goharbor/harbor/src/jobservice/utils" "github.com/goharbor/harbor/src/jobservice/utils"
"github.com/gomodule/redigo/redis" "github.com/gomodule/redigo/redis"
@ -16,29 +17,53 @@ import (
// Once a job is declared to be unique, the job can be enqueued only if // Once a job is declared to be unique, the job can be enqueued only if
// no same job (same job name and parameters) in the queue or running in progress. // no same job (same job name and parameters) in the queue or running in progress.
// Adopt the same unique mechanism with the upstream framework. // Adopt the same unique mechanism with the upstream framework.
type DeDuplicator struct { type DeDuplicator interface {
// Check the uniqueness of the unique job and set the unique flag if it is not set yet.
//
// Parameters:
// jobName string : name of the job
// params models.Parameters : parameters of the job
//
// Returns:
// If no unique flag and successfully set it, a nil error is returned;
// otherwise, a non nil error is returned.
Unique(jobName string, params models.Parameters) error
// Remove the unique flag after job exiting
// Parameters:
// jobName string : name of the job
// params models.Parameters : parameters of the job
//
// Returns:
// If unique flag is successfully removed, a nil error is returned;
// otherwise, a non nil error is returned.
DelUniqueSign(jobName string, params models.Parameters) error
}
// RedisDeDuplicator implement the DeDuplicator interface based on redis.
type RedisDeDuplicator struct {
// Redis namespace // Redis namespace
namespace string namespace string
// Redis conn pool // Redis conn pool
pool *redis.Pool pool *redis.Pool
} }
// NewDeDuplicator is constructor of DeDuplicator // NewRedisDeDuplicator is constructor of RedisDeDuplicator
func NewDeDuplicator(ns string, pool *redis.Pool) *DeDuplicator { func NewRedisDeDuplicator(ns string, pool *redis.Pool) *RedisDeDuplicator {
return &DeDuplicator{ return &RedisDeDuplicator{
namespace: ns, namespace: ns,
pool: pool, pool: pool,
} }
} }
// Unique checks if the job is unique and set unique flag if it is not set yet. // Unique checks if the job is unique and set unique flag if it is not set yet.
func (dd *DeDuplicator) Unique(jobName string, params models.Parameters) error { func (rdd *RedisDeDuplicator) Unique(jobName string, params models.Parameters) error {
uniqueKey, err := redisKeyUniqueJob(dd.namespace, jobName, params) uniqueKey, err := redisKeyUniqueJob(rdd.namespace, jobName, params)
if err != nil { if err != nil {
return fmt.Errorf("unique job error: %s", err) return fmt.Errorf("unique job error: %s", err)
} }
conn := dd.pool.Get() conn := rdd.pool.Get()
defer conn.Close() defer conn.Close()
args := []interface{}{ args := []interface{}{
@ -50,21 +75,29 @@ func (dd *DeDuplicator) Unique(jobName string, params models.Parameters) error {
} }
res, err := redis.String(conn.Do("SET", args...)) res, err := redis.String(conn.Do("SET", args...))
if err == nil && strings.ToUpper(res) == "OK" { if err == redis.ErrNil {
return errs.ConflictError(uniqueKey)
}
if err == nil {
if strings.ToUpper(res) == "OK" {
return nil return nil
} }
return errors.New("unique job error: duplicated") return errors.New("unique job error: missing 'OK' reply")
}
return err
} }
// DelUniqueSign delete the job unique sign // DelUniqueSign delete the job unique sign
func (dd *DeDuplicator) DelUniqueSign(jobName string, params models.Parameters) error { func (rdd *RedisDeDuplicator) DelUniqueSign(jobName string, params models.Parameters) error {
uniqueKey, err := redisKeyUniqueJob(dd.namespace, jobName, params) uniqueKey, err := redisKeyUniqueJob(rdd.namespace, jobName, params)
if err != nil { if err != nil {
return fmt.Errorf("delete unique job error: %s", err) return fmt.Errorf("delete unique job error: %s", err)
} }
conn := dd.pool.Get() conn := rdd.pool.Get()
defer conn.Close() defer conn.Close()
if _, err := conn.Do("DEL", uniqueKey); err != nil { if _, err := conn.Do("DEL", uniqueKey); err != nil {

View File

@ -0,0 +1,28 @@
package pool
import (
"testing"
"github.com/goharbor/harbor/src/jobservice/tests"
)
func TestDeDuplicator(t *testing.T) {
jobName := "fake_job"
jobParams := map[string]interface{}{
"image": "ubuntu:latest",
}
rdd := NewRedisDeDuplicator(tests.GiveMeTestNamespace(), rPool)
if err := rdd.Unique(jobName, jobParams); err != nil {
t.Error(err)
}
if err := rdd.Unique(jobName, jobParams); err == nil {
t.Errorf("expect duplicated error but got nil error")
}
if err := rdd.DelUniqueSign(jobName, jobParams); err != nil {
t.Error(err)
}
}

View File

@ -37,11 +37,11 @@ type RedisJob struct {
job interface{} // the real job implementation job interface{} // the real job implementation
context *env.Context // context context *env.Context // context
statsManager opm.JobStatsManager // job stats manager statsManager opm.JobStatsManager // job stats manager
deDuplicator *DeDuplicator // handle unique job deDuplicator DeDuplicator // handle unique job
} }
// NewRedisJob is constructor of RedisJob // NewRedisJob is constructor of RedisJob
func NewRedisJob(j interface{}, ctx *env.Context, statsManager opm.JobStatsManager, deDuplicator *DeDuplicator) *RedisJob { func NewRedisJob(j interface{}, ctx *env.Context, statsManager opm.JobStatsManager, deDuplicator DeDuplicator) *RedisJob {
return &RedisJob{ return &RedisJob{
job: j, job: j,
context: ctx, context: ctx,

View File

@ -50,7 +50,8 @@ func TestJobWrapper(t *testing.T) {
WG: &sync.WaitGroup{}, WG: &sync.WaitGroup{},
ErrorChan: make(chan error, 1), // with 1 buffer ErrorChan: make(chan error, 1), // with 1 buffer
} }
wrapper := NewRedisJob((*fakeParentJob)(nil), envContext, mgr) deDuplicator := NewRedisDeDuplicator(tests.GiveMeTestNamespace(), rPool)
wrapper := NewRedisJob((*fakeParentJob)(nil), envContext, mgr, deDuplicator)
j := &work.Job{ j := &work.Job{
ID: "FAKE", ID: "FAKE",
Name: "DEMO", Name: "DEMO",

View File

@ -59,7 +59,7 @@ type GoCraftWorkPool struct {
scheduler period.Interface scheduler period.Interface
statsManager opm.JobStatsManager statsManager opm.JobStatsManager
messageServer *MessageServer messageServer *MessageServer
deDuplicator *DeDuplicator deDuplicator DeDuplicator
// no need to sync as write once and then only read // no need to sync as write once and then only read
// key is name of known job // key is name of known job
@ -80,7 +80,7 @@ func NewGoCraftWorkPool(ctx *env.Context, namespace string, workerCount uint, re
scheduler := period.NewRedisPeriodicScheduler(ctx, namespace, redisPool, statsMgr) scheduler := period.NewRedisPeriodicScheduler(ctx, namespace, redisPool, statsMgr)
sweeper := period.NewSweeper(namespace, redisPool, client) sweeper := period.NewSweeper(namespace, redisPool, client)
msgServer := NewMessageServer(ctx.SystemContext, namespace, redisPool) msgServer := NewMessageServer(ctx.SystemContext, namespace, redisPool)
deDepulicator := NewDeDuplicator(namespace, redisPool) deDepulicator := NewRedisDeDuplicator(namespace, redisPool)
return &GoCraftWorkPool{ return &GoCraftWorkPool{
namespace: namespace, namespace: namespace,
redisPool: redisPool, redisPool: redisPool,