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  }