2022-09-23 00:27:56 -05:00
|
|
|
package scheduler_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2023-07-04 03:03:29 -05:00
|
|
|
"runtime"
|
2022-09-23 00:27:56 -05:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
2022-10-20 11:39:20 -05:00
|
|
|
|
2023-07-04 03:03:29 -05:00
|
|
|
"zotregistry.io/zot/pkg/api/config"
|
2023-12-04 17:13:50 -05:00
|
|
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
2022-09-23 00:27:56 -05:00
|
|
|
"zotregistry.io/zot/pkg/log"
|
|
|
|
"zotregistry.io/zot/pkg/scheduler"
|
|
|
|
)
|
|
|
|
|
|
|
|
type task struct {
|
|
|
|
log log.Logger
|
|
|
|
msg string
|
|
|
|
err bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var errInternal = errors.New("task: internal error")
|
|
|
|
|
2023-09-05 11:48:56 -05:00
|
|
|
func (t *task) DoWork(ctx context.Context) error {
|
2022-09-23 00:27:56 -05:00
|
|
|
if t.err {
|
|
|
|
return errInternal
|
|
|
|
}
|
|
|
|
|
2023-12-11 13:00:34 -05:00
|
|
|
for idx := 0; idx < 5; idx++ {
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
}
|
|
|
|
|
2022-09-23 00:27:56 -05:00
|
|
|
t.log.Info().Msg(t.msg)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-04 17:13:50 -05:00
|
|
|
func (t *task) String() string {
|
|
|
|
return t.Name()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *task) Name() string {
|
|
|
|
return "TestTask"
|
|
|
|
}
|
|
|
|
|
2022-09-23 00:27:56 -05:00
|
|
|
type generator struct {
|
|
|
|
log log.Logger
|
|
|
|
priority string
|
|
|
|
done bool
|
|
|
|
index int
|
|
|
|
step int
|
|
|
|
}
|
|
|
|
|
2023-07-04 03:03:29 -05:00
|
|
|
func (g *generator) Next() (scheduler.Task, error) {
|
2023-12-11 13:00:34 -05:00
|
|
|
if g.step > 100 {
|
2022-09-23 00:27:56 -05:00
|
|
|
g.done = true
|
|
|
|
}
|
|
|
|
g.step++
|
|
|
|
g.index++
|
|
|
|
|
2023-12-11 13:00:34 -05:00
|
|
|
if g.step%11 == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if g.step%13 == 0 {
|
|
|
|
return nil, errInternal
|
|
|
|
}
|
|
|
|
|
2022-09-23 00:27:56 -05:00
|
|
|
return &task{log: g.log, msg: fmt.Sprintf("executing %s task; index: %d", g.priority, g.index), err: false}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) IsDone() bool {
|
|
|
|
return g.done
|
|
|
|
}
|
|
|
|
|
2023-08-07 14:55:19 -05:00
|
|
|
func (g *generator) IsReady() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-09-23 00:27:56 -05:00
|
|
|
func (g *generator) Reset() {
|
|
|
|
g.done = false
|
|
|
|
g.step = 0
|
|
|
|
}
|
|
|
|
|
2022-10-21 10:33:22 -05:00
|
|
|
type shortGenerator struct {
|
|
|
|
log log.Logger
|
|
|
|
priority string
|
|
|
|
done bool
|
|
|
|
index int
|
|
|
|
step int
|
|
|
|
}
|
|
|
|
|
2023-07-04 03:03:29 -05:00
|
|
|
func (g *shortGenerator) Next() (scheduler.Task, error) {
|
2022-10-21 10:33:22 -05:00
|
|
|
g.done = true
|
|
|
|
|
|
|
|
return &task{log: g.log, msg: fmt.Sprintf("executing %s task; index: %d", g.priority, g.index), err: false}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *shortGenerator) IsDone() bool {
|
|
|
|
return g.done
|
|
|
|
}
|
|
|
|
|
2023-08-07 14:55:19 -05:00
|
|
|
func (g *shortGenerator) IsReady() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-10-21 10:33:22 -05:00
|
|
|
func (g *shortGenerator) Reset() {
|
|
|
|
g.done = true
|
|
|
|
g.step = 0
|
|
|
|
}
|
|
|
|
|
2022-09-23 00:27:56 -05:00
|
|
|
func TestScheduler(t *testing.T) {
|
2022-10-21 10:33:22 -05:00
|
|
|
Convey("Test active to waiting periodic generator", t, func() {
|
|
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
|
|
|
|
logger := log.NewLogger("debug", logFile.Name())
|
2023-12-04 17:13:50 -05:00
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(config.New(), metrics, logger)
|
2022-10-21 10:33:22 -05:00
|
|
|
|
|
|
|
genH := &shortGenerator{log: logger, priority: "high priority"}
|
|
|
|
// interval has to be higher than throttle value to simulate
|
2024-01-11 12:30:16 -05:00
|
|
|
sch.SubmitGenerator(genH, 1*time.Second, scheduler.HighPriority)
|
2022-10-21 10:33:22 -05:00
|
|
|
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.RunScheduler()
|
2024-01-11 12:30:16 -05:00
|
|
|
time.Sleep(2 * time.Second)
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.Shutdown()
|
2022-10-21 10:33:22 -05:00
|
|
|
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(string(data), ShouldContainSubstring, "waiting generator is ready, pushing to ready generators")
|
|
|
|
})
|
|
|
|
|
2022-09-23 00:27:56 -05:00
|
|
|
Convey("Test order of generators in queue", t, func() {
|
|
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
|
|
|
|
logger := log.NewLogger("debug", logFile.Name())
|
2023-07-04 03:03:29 -05:00
|
|
|
cfg := config.New()
|
|
|
|
cfg.Scheduler = &config.SchedulerConfig{NumWorkers: 3}
|
2023-12-04 17:13:50 -05:00
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(cfg, metrics, logger)
|
2024-01-11 12:30:16 -05:00
|
|
|
sch.RateLimit = 5 * time.Second
|
2022-09-23 00:27:56 -05:00
|
|
|
|
|
|
|
genL := &generator{log: logger, priority: "low priority"}
|
|
|
|
sch.SubmitGenerator(genL, time.Duration(0), scheduler.LowPriority)
|
|
|
|
|
2023-02-15 14:36:50 -05:00
|
|
|
genM := &generator{log: logger, priority: "medium priority"}
|
|
|
|
sch.SubmitGenerator(genM, time.Duration(0), scheduler.MediumPriority)
|
|
|
|
|
2022-09-23 00:27:56 -05:00
|
|
|
genH := &generator{log: logger, priority: "high priority"}
|
|
|
|
sch.SubmitGenerator(genH, time.Duration(0), scheduler.HighPriority)
|
|
|
|
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.RunScheduler()
|
2023-02-15 14:36:50 -05:00
|
|
|
time.Sleep(4 * time.Second)
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.Shutdown()
|
2022-09-23 00:27:56 -05:00
|
|
|
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
|
|
So(err, ShouldBeNil)
|
2023-02-15 14:36:50 -05:00
|
|
|
|
2022-09-23 00:27:56 -05:00
|
|
|
So(string(data), ShouldContainSubstring, "executing high priority task; index: 1")
|
2023-02-15 14:36:50 -05:00
|
|
|
So(string(data), ShouldContainSubstring, "executing high priority task; index: 2")
|
|
|
|
So(string(data), ShouldNotContainSubstring, "executing medium priority task; index: 1")
|
2023-12-08 03:05:02 -05:00
|
|
|
So(string(data), ShouldNotContainSubstring, "failed to execute task")
|
2022-09-23 00:27:56 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Test task returning an error", t, func() {
|
|
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
|
|
|
|
logger := log.NewLogger("debug", logFile.Name())
|
2023-12-04 17:13:50 -05:00
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(config.New(), metrics, logger)
|
2022-09-23 00:27:56 -05:00
|
|
|
|
|
|
|
t := &task{log: logger, msg: "", err: true}
|
|
|
|
sch.SubmitTask(t, scheduler.MediumPriority)
|
|
|
|
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.RunScheduler()
|
2022-09-23 00:27:56 -05:00
|
|
|
time.Sleep(500 * time.Millisecond)
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.Shutdown()
|
2022-09-23 00:27:56 -05:00
|
|
|
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
|
|
So(err, ShouldBeNil)
|
2023-12-08 03:05:02 -05:00
|
|
|
So(string(data), ShouldContainSubstring, "adding a new task")
|
|
|
|
So(string(data), ShouldContainSubstring, "failed to execute task")
|
2022-09-23 00:27:56 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Test resubmit generator", t, func() {
|
|
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
|
|
|
|
logger := log.NewLogger("debug", logFile.Name())
|
2023-12-04 17:13:50 -05:00
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(config.New(), metrics, logger)
|
2022-09-23 00:27:56 -05:00
|
|
|
|
|
|
|
genL := &generator{log: logger, priority: "low priority"}
|
|
|
|
sch.SubmitGenerator(genL, 20*time.Millisecond, scheduler.LowPriority)
|
|
|
|
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.RunScheduler()
|
2024-01-11 12:30:16 -05:00
|
|
|
time.Sleep(1 * time.Second)
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.Shutdown()
|
2022-09-23 00:27:56 -05:00
|
|
|
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(string(data), ShouldContainSubstring, "executing low priority task; index: 1")
|
|
|
|
So(string(data), ShouldContainSubstring, "executing low priority task; index: 2")
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Try to add a task with wrong priority", t, func() {
|
|
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
|
|
|
|
logger := log.NewLogger("debug", logFile.Name())
|
2023-12-04 17:13:50 -05:00
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(config.New(), metrics, logger)
|
2022-09-23 00:27:56 -05:00
|
|
|
|
|
|
|
t := &task{log: logger, msg: "", err: false}
|
|
|
|
sch.SubmitTask(t, -1)
|
|
|
|
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
|
|
So(err, ShouldBeNil)
|
2023-12-08 03:05:02 -05:00
|
|
|
So(string(data), ShouldNotContainSubstring, "adding a new task")
|
2022-09-23 00:27:56 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Test adding a new task when context is done", t, func() {
|
|
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
|
|
|
|
logger := log.NewLogger("debug", logFile.Name())
|
2023-12-04 17:13:50 -05:00
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(config.New(), metrics, logger)
|
2022-09-23 00:27:56 -05:00
|
|
|
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.RunScheduler()
|
|
|
|
sch.Shutdown()
|
2022-09-23 00:27:56 -05:00
|
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
|
|
|
|
t := &task{log: logger, msg: "", err: false}
|
|
|
|
sch.SubmitTask(t, scheduler.LowPriority)
|
|
|
|
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
|
|
So(err, ShouldBeNil)
|
2023-12-08 03:05:02 -05:00
|
|
|
So(string(data), ShouldNotContainSubstring, "adding a new task")
|
2022-09-23 00:27:56 -05:00
|
|
|
})
|
2023-12-04 17:13:50 -05:00
|
|
|
|
2023-12-11 13:00:34 -05:00
|
|
|
Convey("Test stopping scheduler by calling Shutdown()", t, func() {
|
|
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
|
|
|
|
logger := log.NewLogger("debug", logFile.Name())
|
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(config.New(), metrics, logger)
|
|
|
|
|
|
|
|
genL := &generator{log: logger, priority: "medium priority"}
|
|
|
|
sch.SubmitGenerator(genL, 20*time.Millisecond, scheduler.MediumPriority)
|
|
|
|
|
|
|
|
sch.RunScheduler()
|
2024-01-11 12:30:16 -05:00
|
|
|
time.Sleep(1 * time.Second)
|
2023-12-11 13:00:34 -05:00
|
|
|
sch.Shutdown()
|
|
|
|
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(string(data), ShouldContainSubstring, "executing medium priority task; index: 1")
|
|
|
|
So(string(data), ShouldContainSubstring, "executing medium priority task; index: 2")
|
|
|
|
So(string(data), ShouldContainSubstring, "received stop signal, gracefully shutting down...")
|
|
|
|
})
|
|
|
|
|
2023-12-04 17:13:50 -05:00
|
|
|
Convey("Test scheduler Priority.String() method", t, func() {
|
|
|
|
var p scheduler.Priority //nolint: varnamelen
|
|
|
|
// test invalid priority
|
|
|
|
p = 6238734
|
|
|
|
So(p.String(), ShouldEqual, "invalid")
|
|
|
|
p = scheduler.LowPriority
|
|
|
|
So(p.String(), ShouldEqual, "low")
|
|
|
|
p = scheduler.MediumPriority
|
|
|
|
So(p.String(), ShouldEqual, "medium")
|
|
|
|
p = scheduler.HighPriority
|
|
|
|
So(p.String(), ShouldEqual, "high")
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Test scheduler State.String() method", t, func() {
|
|
|
|
var s scheduler.State //nolint: varnamelen
|
|
|
|
// test invalid state
|
|
|
|
s = -67
|
|
|
|
So(s.String(), ShouldEqual, "invalid")
|
|
|
|
s = scheduler.Ready
|
|
|
|
So(s.String(), ShouldEqual, "ready")
|
|
|
|
s = scheduler.Waiting
|
|
|
|
So(s.String(), ShouldEqual, "waiting")
|
|
|
|
s = scheduler.Done
|
|
|
|
So(s.String(), ShouldEqual, "done")
|
|
|
|
})
|
2022-09-23 00:27:56 -05:00
|
|
|
}
|
2023-07-04 03:03:29 -05:00
|
|
|
|
|
|
|
func TestGetNumWorkers(t *testing.T) {
|
|
|
|
Convey("Test setting the number of workers - default value", t, func() {
|
2023-12-04 17:13:50 -05:00
|
|
|
logger := log.NewLogger("debug", "logFile")
|
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(config.New(), metrics, logger)
|
2023-09-22 08:33:18 -05:00
|
|
|
defer os.Remove("logFile")
|
2023-07-04 03:03:29 -05:00
|
|
|
So(sch.NumWorkers, ShouldEqual, runtime.NumCPU()*4)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Test setting the number of workers - getting the value from config", t, func() {
|
|
|
|
cfg := config.New()
|
|
|
|
cfg.Scheduler = &config.SchedulerConfig{NumWorkers: 3}
|
2023-12-04 17:13:50 -05:00
|
|
|
logger := log.NewLogger("debug", "logFile")
|
|
|
|
metrics := monitoring.NewMetricsServer(true, logger)
|
|
|
|
sch := scheduler.NewScheduler(cfg, metrics, logger)
|
2023-09-22 08:33:18 -05:00
|
|
|
defer os.Remove("logFile")
|
2023-07-04 03:03:29 -05:00
|
|
|
So(sch.NumWorkers, ShouldEqual, 3)
|
|
|
|
})
|
|
|
|
}
|