github.com/grafana/pyroscope@v1.18.0/pkg/metastore/compaction/scheduler/schedule_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/hashicorp/raft"
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/mock"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1"
    13  	"github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1/raft_log"
    14  	"github.com/grafana/pyroscope/pkg/test"
    15  	"github.com/grafana/pyroscope/pkg/test/mocks/mockscheduler"
    16  )
    17  
    18  func TestSchedule_Update_LeaseRenewal(t *testing.T) {
    19  	store := new(mockscheduler.MockJobStore)
    20  	config := Config{
    21  		MaxFailures:   3,
    22  		LeaseDuration: 10 * time.Second,
    23  	}
    24  
    25  	scheduler := NewScheduler(config, store, nil)
    26  	scheduler.queue.put(&raft_log.CompactionJobState{
    27  		Name:            "1",
    28  		CompactionLevel: 0,
    29  		Status:          metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS,
    30  		Token:           1,
    31  		LeaseExpiresAt:  0,
    32  	})
    33  
    34  	t.Run("Owner", test.AssertIdempotentSubtest(t, func(t *testing.T) {
    35  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 0)})
    36  		update := s.UpdateJob(&raft_log.CompactionJobStatusUpdate{
    37  			Name:   "1",
    38  			Token:  1,
    39  			Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS,
    40  		})
    41  		assert.Equal(t, &raft_log.CompactionJobState{
    42  			Name:            "1",
    43  			CompactionLevel: 0,
    44  			Status:          metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS,
    45  			Token:           1,
    46  			LeaseExpiresAt:  int64(config.LeaseDuration),
    47  		}, update)
    48  	}))
    49  
    50  	t.Run("NotOwner", test.AssertIdempotentSubtest(t, func(t *testing.T) {
    51  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 0)})
    52  		assert.Nil(t, s.UpdateJob(&raft_log.CompactionJobStatusUpdate{
    53  			Name:   "1",
    54  			Token:  0,
    55  			Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS,
    56  		}))
    57  	}))
    58  
    59  	t.Run("JobCompleted", test.AssertIdempotentSubtest(t, func(t *testing.T) {
    60  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 0)})
    61  		assert.Nil(t, s.UpdateJob(&raft_log.CompactionJobStatusUpdate{
    62  			Name:   "0",
    63  			Token:  1,
    64  			Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS,
    65  		}))
    66  	}))
    67  
    68  	t.Run("WrongStatus", test.AssertIdempotentSubtest(t, func(t *testing.T) {
    69  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 0)})
    70  		assert.Nil(t, s.UpdateJob(&raft_log.CompactionJobStatusUpdate{
    71  			Name:   "1",
    72  			Token:  1,
    73  			Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED,
    74  		}))
    75  	}))
    76  }
    77  
    78  func TestSchedule_Update_JobCompleted(t *testing.T) {
    79  	store := new(mockscheduler.MockJobStore)
    80  	config := Config{
    81  		MaxFailures:   3,
    82  		LeaseDuration: 10 * time.Second,
    83  	}
    84  
    85  	scheduler := NewScheduler(config, store, nil)
    86  	scheduler.queue.put(&raft_log.CompactionJobState{
    87  		Name:            "1",
    88  		CompactionLevel: 1,
    89  		Status:          metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS,
    90  		Token:           1,
    91  	})
    92  
    93  	t.Run("Owner", test.AssertIdempotentSubtest(t, func(t *testing.T) {
    94  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 0)})
    95  		update := s.UpdateJob(&raft_log.CompactionJobStatusUpdate{
    96  			Name:   "1",
    97  			Token:  1,
    98  			Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_SUCCESS,
    99  		})
   100  		assert.Equal(t, &raft_log.CompactionJobState{
   101  			Name:            "1",
   102  			CompactionLevel: 1,
   103  			Status:          metastorev1.CompactionJobStatus_COMPACTION_STATUS_SUCCESS,
   104  			Token:           1,
   105  		}, update)
   106  	}))
   107  
   108  	t.Run("NotOwner", test.AssertIdempotentSubtest(t, func(t *testing.T) {
   109  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 0)})
   110  		assert.Nil(t, s.UpdateJob(&raft_log.CompactionJobStatusUpdate{
   111  			Name:   "1",
   112  			Token:  0,
   113  			Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_SUCCESS,
   114  		}))
   115  	}))
   116  }
   117  
   118  func TestSchedule_Assign(t *testing.T) {
   119  	store := new(mockscheduler.MockJobStore)
   120  	config := Config{
   121  		MaxFailures:   3,
   122  		LeaseDuration: 10 * time.Second,
   123  	}
   124  
   125  	scheduler := NewScheduler(config, store, nil)
   126  	// The job plans are accessed when it's getting assigned.
   127  	// Their content is not important for the test.
   128  	plans := []*raft_log.CompactionJobPlan{
   129  		{Name: "2", CompactionLevel: 0},
   130  		{Name: "3", CompactionLevel: 0},
   131  		{Name: "1", CompactionLevel: 1},
   132  	}
   133  	for _, p := range plans {
   134  		store.On("GetJobPlan", mock.Anything, p.Name).Return(p, nil)
   135  	}
   136  
   137  	states := []*raft_log.CompactionJobState{
   138  		{Name: "1", CompactionLevel: 1, Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED},
   139  		{Name: "2", CompactionLevel: 0, Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED},
   140  		{Name: "3", CompactionLevel: 0, Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED},
   141  		{Name: "4", CompactionLevel: 0, Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS},
   142  		{Name: "5", CompactionLevel: 0, Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS},
   143  	}
   144  	for _, s := range states {
   145  		scheduler.queue.put(s)
   146  	}
   147  
   148  	test.AssertIdempotent(t, func(t *testing.T) {
   149  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 0)})
   150  		for j := range plans {
   151  			update, err := s.AssignJob()
   152  			require.NoError(t, err)
   153  			assert.Equal(t, plans[j], update.Plan)
   154  			assert.Equal(t, metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, update.State.Status)
   155  			assert.Equal(t, int64(config.LeaseDuration), update.State.LeaseExpiresAt)
   156  			assert.Equal(t, uint64(1), update.State.Token)
   157  		}
   158  
   159  		update, err := s.AssignJob()
   160  		require.NoError(t, err)
   161  		assert.Nil(t, update)
   162  	})
   163  }
   164  
   165  func TestSchedule_ReAssign(t *testing.T) {
   166  	store := new(mockscheduler.MockJobStore)
   167  	config := Config{
   168  		MaxFailures:   3,
   169  		LeaseDuration: 10 * time.Second,
   170  	}
   171  
   172  	scheduler := NewScheduler(config, store, nil)
   173  	plans := []*raft_log.CompactionJobPlan{
   174  		{Name: "1"},
   175  		{Name: "2"},
   176  		{Name: "3"},
   177  		{Name: "4"},
   178  		{Name: "5"},
   179  		{Name: "6"},
   180  	}
   181  	for _, p := range plans {
   182  		store.On("GetJobPlan", mock.Anything, p.Name).Return(p, nil)
   183  	}
   184  
   185  	now := int64(5)
   186  	states := []*raft_log.CompactionJobState{
   187  		// Jobs with expired leases (now > LeaseExpiresAt).
   188  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 1},
   189  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 1},
   190  		{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 1},
   191  		// This job can't be reassigned as its lease is still valid.
   192  		{Name: "4", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 10},
   193  		// The job has already failed in the past.
   194  		{Name: "5", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 1, Failures: 1},
   195  		// The job has already failed in the past and exceeded the error threshold.
   196  		{Name: "6", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 1, Failures: 3},
   197  	}
   198  	for _, s := range states {
   199  		scheduler.queue.put(s)
   200  	}
   201  
   202  	lease := now + int64(config.LeaseDuration)
   203  	expected := []*raft_log.CompactionJobState{
   204  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 2, LeaseExpiresAt: lease, Failures: 1},
   205  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 2, LeaseExpiresAt: lease, Failures: 1},
   206  		{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 2, LeaseExpiresAt: lease, Failures: 1},
   207  		{Name: "5", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 2, LeaseExpiresAt: lease, Failures: 2},
   208  	}
   209  
   210  	test.AssertIdempotent(t, func(t *testing.T) {
   211  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 2, AppendedAt: time.Unix(0, now)})
   212  		assigned := make([]*raft_log.CompactionJobState, 0, len(expected))
   213  		for {
   214  			update, err := s.AssignJob()
   215  			require.NoError(t, err)
   216  			if update == nil {
   217  				break
   218  			}
   219  			assigned = append(assigned, update.State)
   220  		}
   221  
   222  		assert.Equal(t, expected, assigned)
   223  	})
   224  }
   225  
   226  func TestSchedule_UpdateAssign(t *testing.T) {
   227  	store := new(mockscheduler.MockJobStore)
   228  	config := Config{
   229  		MaxFailures:   3,
   230  		LeaseDuration: 10 * time.Second,
   231  	}
   232  
   233  	scheduler := NewScheduler(config, store, nil)
   234  	plans := []*raft_log.CompactionJobPlan{
   235  		{Name: "1"},
   236  		{Name: "2"},
   237  		{Name: "3"},
   238  	}
   239  	for _, p := range plans {
   240  		store.On("GetJobPlan", mock.Anything, p.Name).Return(p, nil)
   241  	}
   242  
   243  	states := []*raft_log.CompactionJobState{
   244  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0},
   245  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0},
   246  		{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0},
   247  	}
   248  	for _, s := range states {
   249  		scheduler.queue.put(s)
   250  	}
   251  
   252  	// Lease is extended without reassignment if update arrives after the
   253  	// expiration, but this is the first worker requested assignment.
   254  	test.AssertIdempotent(t, func(t *testing.T) {
   255  		updates := []*raft_log.CompactionJobStatusUpdate{
   256  			{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1},
   257  			{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1},
   258  			{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1},
   259  		}
   260  
   261  		updatedAt := time.Second * 20
   262  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 2, AppendedAt: time.Unix(0, int64(updatedAt))})
   263  		for i := range updates {
   264  			update := s.UpdateJob(updates[i])
   265  			assert.Equal(t, metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, update.Status)
   266  			assert.Equal(t, int64(updatedAt)+int64(config.LeaseDuration), update.LeaseExpiresAt)
   267  			assert.Equal(t, uint64(1), update.Token) // Token must not change.
   268  		}
   269  
   270  		update, err := s.AssignJob()
   271  		require.NoError(t, err)
   272  		assert.Nil(t, update)
   273  	})
   274  
   275  	// If the worker reports success status and its lease has expired but the
   276  	// job has not been reassigned, we accept the results.
   277  	test.AssertIdempotent(t, func(t *testing.T) {
   278  		updates := []*raft_log.CompactionJobStatusUpdate{
   279  			{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_SUCCESS, Token: 1},
   280  			{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_SUCCESS, Token: 1},
   281  			{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_SUCCESS, Token: 1},
   282  		}
   283  
   284  		updatedAt := time.Second * 20
   285  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 2, AppendedAt: time.Unix(0, int64(updatedAt))})
   286  		for i := range updates {
   287  			assert.NotNil(t, s.UpdateJob(updates[i]))
   288  		}
   289  
   290  		update, err := s.AssignJob()
   291  		require.NoError(t, err)
   292  		assert.Nil(t, update)
   293  	})
   294  
   295  	// The worker may be reassigned with the jobs it abandoned,
   296  	// if it requested assignments first.
   297  	test.AssertIdempotent(t, func(t *testing.T) {
   298  		updatedAt := time.Second * 20
   299  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 2, AppendedAt: time.Unix(0, int64(updatedAt))})
   300  		for range plans {
   301  			update, err := s.AssignJob()
   302  			require.NoError(t, err)
   303  			assert.NotNil(t, update.State)
   304  			assert.NotNil(t, update.Plan)
   305  			assert.Equal(t, int64(updatedAt)+int64(config.LeaseDuration), update.State.LeaseExpiresAt)
   306  			assert.Equal(t, uint64(2), update.State.Token) // Token must change.
   307  		}
   308  
   309  		update, err := s.AssignJob()
   310  		require.NoError(t, err)
   311  		assert.Nil(t, update)
   312  	})
   313  }
   314  
   315  func TestSchedule_Add(t *testing.T) {
   316  	store := new(mockscheduler.MockJobStore)
   317  	config := Config{
   318  		MaxFailures:   3,
   319  		LeaseDuration: 10 * time.Second,
   320  	}
   321  
   322  	scheduler := NewScheduler(config, store, nil)
   323  	plans := []*raft_log.CompactionJobPlan{
   324  		{Name: "1"},
   325  		{Name: "2"},
   326  		{Name: "3"},
   327  	}
   328  
   329  	states := []*raft_log.CompactionJobState{
   330  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED, AddedAt: 1, Token: 1},
   331  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED, AddedAt: 1, Token: 1},
   332  		{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED, AddedAt: 1, Token: 1},
   333  	}
   334  
   335  	test.AssertIdempotent(t, func(t *testing.T) {
   336  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 1)})
   337  		for i := range plans {
   338  			assert.Equal(t, states[i], s.AddJob(plans[i]))
   339  		}
   340  	})
   341  }
   342  
   343  func TestSchedule_QueueSizeLimit(t *testing.T) {
   344  	store := new(mockscheduler.MockJobStore)
   345  	config := Config{
   346  		MaxQueueSize:  2,
   347  		MaxFailures:   3,
   348  		LeaseDuration: 10 * time.Second,
   349  	}
   350  
   351  	scheduler := NewScheduler(config, store, nil)
   352  	plans := []*raft_log.CompactionJobPlan{
   353  		{Name: "1"},
   354  		{Name: "2"},
   355  		{Name: "3"},
   356  	}
   357  
   358  	states := []*raft_log.CompactionJobState{
   359  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED, AddedAt: 1, Token: 1},
   360  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_UNSPECIFIED, AddedAt: 1, Token: 1},
   361  	}
   362  
   363  	test.AssertIdempotent(t, func(t *testing.T) {
   364  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 1, AppendedAt: time.Unix(0, 1)})
   365  		assert.Equal(t, states[0], s.AddJob(plans[0]))
   366  		assert.Equal(t, states[1], s.AddJob(plans[1]))
   367  		assert.Nil(t, s.AddJob(plans[2]))
   368  	})
   369  }
   370  
   371  func TestSchedule_AssignEvict(t *testing.T) {
   372  	store := new(mockscheduler.MockJobStore)
   373  	config := Config{
   374  		MaxQueueSize:  2,
   375  		MaxFailures:   3,
   376  		LeaseDuration: 10 * time.Second,
   377  	}
   378  
   379  	scheduler := NewScheduler(config, store, nil)
   380  	plans := []*raft_log.CompactionJobPlan{
   381  		{Name: "4"},
   382  	}
   383  	for _, p := range plans {
   384  		store.On("GetJobPlan", mock.Anything, p.Name).Return(p, nil)
   385  	}
   386  
   387  	states := []*raft_log.CompactionJobState{
   388  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   389  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   390  		{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   391  		{Name: "4", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 0},
   392  	}
   393  	for _, s := range states {
   394  		scheduler.queue.put(s)
   395  	}
   396  
   397  	test.AssertIdempotent(t, func(t *testing.T) {
   398  		updatedAt := time.Second * 20
   399  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 2, AppendedAt: time.Unix(0, int64(updatedAt))})
   400  		// Eviction is only possible when no jobs are available for assignment.
   401  		assert.Nil(t, s.EvictJob())
   402  		// Assign all the available jobs.
   403  		update, err := s.AssignJob()
   404  		require.NoError(t, err)
   405  		assert.Equal(t, "4", update.State.Name)
   406  		update, err = s.AssignJob()
   407  		require.NoError(t, err)
   408  		assert.Nil(t, update)
   409  		// Now that no jobs can be assigned, we can try eviction.
   410  		assert.NotNil(t, s.EvictJob())
   411  		assert.NotNil(t, s.EvictJob())
   412  		// MaxQueueSize reached.
   413  		assert.Nil(t, s.EvictJob())
   414  	})
   415  }
   416  
   417  func TestSchedule_Evict(t *testing.T) {
   418  	store := new(mockscheduler.MockJobStore)
   419  	config := Config{
   420  		MaxQueueSize:  2,
   421  		MaxFailures:   3,
   422  		LeaseDuration: 10 * time.Second,
   423  	}
   424  
   425  	scheduler := NewScheduler(config, store, nil)
   426  	states := []*raft_log.CompactionJobState{
   427  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   428  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   429  		{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   430  	}
   431  	for _, s := range states {
   432  		scheduler.queue.put(s)
   433  	}
   434  
   435  	test.AssertIdempotent(t, func(t *testing.T) {
   436  		updatedAt := time.Second * 20
   437  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 2, AppendedAt: time.Unix(0, int64(updatedAt))})
   438  		// Eviction is only possible when no jobs are available for assignment.
   439  		update, err := s.AssignJob()
   440  		require.NoError(t, err)
   441  		assert.Nil(t, update)
   442  		assert.NotNil(t, s.EvictJob())
   443  		assert.Nil(t, s.EvictJob())
   444  	})
   445  }
   446  
   447  func TestSchedule_NoEvict(t *testing.T) {
   448  	store := new(mockscheduler.MockJobStore)
   449  	config := Config{
   450  		MaxQueueSize:  5,
   451  		MaxFailures:   3,
   452  		LeaseDuration: 10 * time.Second,
   453  	}
   454  
   455  	scheduler := NewScheduler(config, store, nil)
   456  	states := []*raft_log.CompactionJobState{
   457  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   458  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   459  		{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   460  	}
   461  	for _, s := range states {
   462  		scheduler.queue.put(s)
   463  	}
   464  
   465  	test.AssertIdempotent(t, func(t *testing.T) {
   466  		updatedAt := time.Second * 20
   467  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 2, AppendedAt: time.Unix(0, int64(updatedAt))})
   468  		// Eviction is only possible when no jobs are available for assignment.
   469  		update, err := s.AssignJob()
   470  		require.NoError(t, err)
   471  		assert.Nil(t, update)
   472  		// Eviction is only possible when the queue size limit is reached.
   473  		assert.Nil(t, s.EvictJob())
   474  	})
   475  }
   476  
   477  func TestSchedule_NoEvictNoQueueSizeLimit(t *testing.T) {
   478  	store := new(mockscheduler.MockJobStore)
   479  	config := Config{
   480  		MaxQueueSize:  0,
   481  		MaxFailures:   3,
   482  		LeaseDuration: 10 * time.Second,
   483  	}
   484  
   485  	scheduler := NewScheduler(config, store, nil)
   486  	states := []*raft_log.CompactionJobState{
   487  		{Name: "1", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   488  		{Name: "2", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   489  		{Name: "3", Status: metastorev1.CompactionJobStatus_COMPACTION_STATUS_IN_PROGRESS, Token: 1, LeaseExpiresAt: 0, Failures: 3},
   490  	}
   491  	for _, s := range states {
   492  		scheduler.queue.put(s)
   493  	}
   494  
   495  	test.AssertIdempotent(t, func(t *testing.T) {
   496  		updatedAt := time.Second * 20
   497  		s := scheduler.NewSchedule(nil, &raft.Log{Index: 2, AppendedAt: time.Unix(0, int64(updatedAt))})
   498  		// Eviction is only possible when no jobs are available for assignment.
   499  		update, err := s.AssignJob()
   500  		require.NoError(t, err)
   501  		assert.Nil(t, update)
   502  		// Eviction is not possible if the queue size limit is not set.
   503  		assert.Nil(t, s.EvictJob())
   504  	})
   505  }