zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/scheduler/scheduler_test.go (about) 1 package scheduler_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 "runtime" 9 "testing" 10 "time" 11 12 . "github.com/smartystreets/goconvey/convey" 13 14 "zotregistry.io/zot/pkg/api/config" 15 "zotregistry.io/zot/pkg/log" 16 "zotregistry.io/zot/pkg/scheduler" 17 ) 18 19 type task struct { 20 log log.Logger 21 msg string 22 err bool 23 } 24 25 var errInternal = errors.New("task: internal error") 26 27 func (t *task) DoWork(ctx context.Context) error { 28 if t.err { 29 return errInternal 30 } 31 32 t.log.Info().Msg(t.msg) 33 34 return nil 35 } 36 37 type generator struct { 38 log log.Logger 39 priority string 40 done bool 41 index int 42 step int 43 } 44 45 func (g *generator) Next() (scheduler.Task, error) { 46 if g.step > 1 { 47 g.done = true 48 } 49 g.step++ 50 g.index++ 51 52 return &task{log: g.log, msg: fmt.Sprintf("executing %s task; index: %d", g.priority, g.index), err: false}, nil 53 } 54 55 func (g *generator) IsDone() bool { 56 return g.done 57 } 58 59 func (g *generator) IsReady() bool { 60 return true 61 } 62 63 func (g *generator) Reset() { 64 g.done = false 65 g.step = 0 66 } 67 68 type shortGenerator struct { 69 log log.Logger 70 priority string 71 done bool 72 index int 73 step int 74 } 75 76 func (g *shortGenerator) Next() (scheduler.Task, error) { 77 g.done = true 78 79 return &task{log: g.log, msg: fmt.Sprintf("executing %s task; index: %d", g.priority, g.index), err: false}, nil 80 } 81 82 func (g *shortGenerator) IsDone() bool { 83 return g.done 84 } 85 86 func (g *shortGenerator) IsReady() bool { 87 return true 88 } 89 90 func (g *shortGenerator) Reset() { 91 g.done = true 92 g.step = 0 93 } 94 95 func TestScheduler(t *testing.T) { 96 Convey("Test active to waiting periodic generator", t, func() { 97 logFile, err := os.CreateTemp("", "zot-log*.txt") 98 So(err, ShouldBeNil) 99 100 defer os.Remove(logFile.Name()) // clean up 101 102 logger := log.NewLogger("debug", logFile.Name()) 103 sch := scheduler.NewScheduler(config.New(), logger) 104 105 genH := &shortGenerator{log: logger, priority: "high priority"} 106 // interval has to be higher than throttle value to simulate 107 sch.SubmitGenerator(genH, 12000*time.Millisecond, scheduler.HighPriority) 108 109 ctx, cancel := context.WithCancel(context.Background()) 110 sch.RunScheduler(ctx) 111 112 time.Sleep(16 * time.Second) 113 cancel() 114 115 data, err := os.ReadFile(logFile.Name()) 116 So(err, ShouldBeNil) 117 So(string(data), ShouldContainSubstring, "waiting generator is ready, pushing to ready generators") 118 }) 119 120 Convey("Test order of generators in queue", t, func() { 121 logFile, err := os.CreateTemp("", "zot-log*.txt") 122 So(err, ShouldBeNil) 123 124 defer os.Remove(logFile.Name()) // clean up 125 126 logger := log.NewLogger("debug", logFile.Name()) 127 cfg := config.New() 128 cfg.Scheduler = &config.SchedulerConfig{NumWorkers: 3} 129 sch := scheduler.NewScheduler(cfg, logger) 130 131 genL := &generator{log: logger, priority: "low priority"} 132 sch.SubmitGenerator(genL, time.Duration(0), scheduler.LowPriority) 133 134 genM := &generator{log: logger, priority: "medium priority"} 135 sch.SubmitGenerator(genM, time.Duration(0), scheduler.MediumPriority) 136 137 genH := &generator{log: logger, priority: "high priority"} 138 sch.SubmitGenerator(genH, time.Duration(0), scheduler.HighPriority) 139 140 ctx, cancel := context.WithCancel(context.Background()) 141 142 sch.RunScheduler(ctx) 143 144 time.Sleep(4 * time.Second) 145 cancel() 146 147 data, err := os.ReadFile(logFile.Name()) 148 So(err, ShouldBeNil) 149 150 So(string(data), ShouldContainSubstring, "executing high priority task; index: 1") 151 So(string(data), ShouldContainSubstring, "executing high priority task; index: 2") 152 So(string(data), ShouldNotContainSubstring, "executing medium priority task; index: 1") 153 So(string(data), ShouldNotContainSubstring, "error while executing task") 154 }) 155 156 Convey("Test task returning an error", t, func() { 157 logFile, err := os.CreateTemp("", "zot-log*.txt") 158 So(err, ShouldBeNil) 159 160 defer os.Remove(logFile.Name()) // clean up 161 162 logger := log.NewLogger("debug", logFile.Name()) 163 sch := scheduler.NewScheduler(config.New(), logger) 164 165 t := &task{log: logger, msg: "", err: true} 166 sch.SubmitTask(t, scheduler.MediumPriority) 167 168 ctx, cancel := context.WithCancel(context.Background()) 169 sch.RunScheduler(ctx) 170 171 time.Sleep(500 * time.Millisecond) 172 cancel() 173 174 data, err := os.ReadFile(logFile.Name()) 175 So(err, ShouldBeNil) 176 So(string(data), ShouldContainSubstring, "scheduler: adding a new task") 177 So(string(data), ShouldContainSubstring, "error while executing task") 178 }) 179 180 Convey("Test resubmit generator", t, func() { 181 logFile, err := os.CreateTemp("", "zot-log*.txt") 182 So(err, ShouldBeNil) 183 184 defer os.Remove(logFile.Name()) // clean up 185 186 logger := log.NewLogger("debug", logFile.Name()) 187 sch := scheduler.NewScheduler(config.New(), logger) 188 189 genL := &generator{log: logger, priority: "low priority"} 190 sch.SubmitGenerator(genL, 20*time.Millisecond, scheduler.LowPriority) 191 192 ctx, cancel := context.WithCancel(context.Background()) 193 sch.RunScheduler(ctx) 194 195 time.Sleep(6 * time.Second) 196 cancel() 197 198 data, err := os.ReadFile(logFile.Name()) 199 So(err, ShouldBeNil) 200 So(string(data), ShouldContainSubstring, "executing low priority task; index: 1") 201 So(string(data), ShouldContainSubstring, "executing low priority task; index: 2") 202 }) 203 204 Convey("Try to add a task with wrong priority", t, func() { 205 logFile, err := os.CreateTemp("", "zot-log*.txt") 206 So(err, ShouldBeNil) 207 208 defer os.Remove(logFile.Name()) // clean up 209 210 logger := log.NewLogger("debug", logFile.Name()) 211 sch := scheduler.NewScheduler(config.New(), logger) 212 213 t := &task{log: logger, msg: "", err: false} 214 sch.SubmitTask(t, -1) 215 216 data, err := os.ReadFile(logFile.Name()) 217 So(err, ShouldBeNil) 218 So(string(data), ShouldNotContainSubstring, "scheduler: adding a new task") 219 }) 220 221 Convey("Test adding a new task when context is done", t, func() { 222 logFile, err := os.CreateTemp("", "zot-log*.txt") 223 So(err, ShouldBeNil) 224 225 defer os.Remove(logFile.Name()) // clean up 226 227 logger := log.NewLogger("debug", logFile.Name()) 228 sch := scheduler.NewScheduler(config.New(), logger) 229 230 ctx, cancel := context.WithCancel(context.Background()) 231 232 sch.RunScheduler(ctx) 233 cancel() 234 time.Sleep(500 * time.Millisecond) 235 236 t := &task{log: logger, msg: "", err: false} 237 sch.SubmitTask(t, scheduler.LowPriority) 238 239 data, err := os.ReadFile(logFile.Name()) 240 So(err, ShouldBeNil) 241 So(string(data), ShouldNotContainSubstring, "scheduler: adding a new task") 242 }) 243 } 244 245 func TestGetNumWorkers(t *testing.T) { 246 Convey("Test setting the number of workers - default value", t, func() { 247 sch := scheduler.NewScheduler(config.New(), log.NewLogger("debug", "logFile")) 248 defer os.Remove("logFile") 249 So(sch.NumWorkers, ShouldEqual, runtime.NumCPU()*4) 250 }) 251 252 Convey("Test setting the number of workers - getting the value from config", t, func() { 253 cfg := config.New() 254 cfg.Scheduler = &config.SchedulerConfig{NumWorkers: 3} 255 sch := scheduler.NewScheduler(cfg, log.NewLogger("debug", "logFile")) 256 defer os.Remove("logFile") 257 So(sch.NumWorkers, ShouldEqual, 3) 258 }) 259 }