mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
434 lines
12 KiB
434 lines
12 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package scheduler
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/iam"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
"github.com/hashicorp/boundary/internal/scheduler/job"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestScheduler_New(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
iam.TestRepo(t, conn, wrapper)
|
|
|
|
jobRepoFn := func() (*job.Repository, error) {
|
|
return job.NewRepository(ctx, rw, rw, kmsCache)
|
|
}
|
|
|
|
type args struct {
|
|
serverId string
|
|
jobRepo jobRepoFactory
|
|
runInterval time.Duration
|
|
monitorInterval time.Duration
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
opts []Option
|
|
want args
|
|
wantErr bool
|
|
wantErrCode errors.Code
|
|
wantErrMsg string
|
|
}{
|
|
{
|
|
name: "with-no-server-id",
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
wantErrMsg: "scheduler.New: missing server id: parameter violation: error #100",
|
|
},
|
|
{
|
|
name: "with-no-job-repo",
|
|
args: args{
|
|
serverId: "test-server",
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
wantErrMsg: "scheduler.New: missing job repo function: parameter violation: error #100",
|
|
},
|
|
{
|
|
name: "valid",
|
|
args: args{
|
|
serverId: "test-server",
|
|
jobRepo: jobRepoFn,
|
|
},
|
|
want: args{
|
|
serverId: "test-server",
|
|
runInterval: defaultRunJobsInterval,
|
|
monitorInterval: defaultMonitorInterval,
|
|
},
|
|
},
|
|
{
|
|
name: "valid-with-interval",
|
|
args: args{
|
|
serverId: "test-server",
|
|
jobRepo: jobRepoFn,
|
|
},
|
|
opts: []Option{
|
|
WithRunJobsInterval(time.Hour),
|
|
},
|
|
want: args{
|
|
serverId: "test-server",
|
|
monitorInterval: defaultMonitorInterval,
|
|
runInterval: time.Hour,
|
|
},
|
|
},
|
|
{
|
|
name: "valid-with-unlimited",
|
|
args: args{
|
|
serverId: "test-server",
|
|
jobRepo: jobRepoFn,
|
|
},
|
|
opts: []Option{},
|
|
want: args{
|
|
serverId: "test-server",
|
|
runInterval: defaultRunJobsInterval,
|
|
monitorInterval: defaultMonitorInterval,
|
|
},
|
|
},
|
|
{
|
|
name: "valid-with-limit",
|
|
args: args{
|
|
serverId: "test-server",
|
|
jobRepo: jobRepoFn,
|
|
},
|
|
opts: []Option{},
|
|
want: args{
|
|
serverId: "test-server",
|
|
runInterval: defaultRunJobsInterval,
|
|
monitorInterval: defaultMonitorInterval,
|
|
},
|
|
},
|
|
{
|
|
name: "valid-with-monitor",
|
|
args: args{
|
|
serverId: "test-server",
|
|
jobRepo: jobRepoFn,
|
|
},
|
|
opts: []Option{
|
|
WithMonitorInterval(time.Hour),
|
|
},
|
|
want: args{
|
|
serverId: "test-server",
|
|
runInterval: defaultRunJobsInterval,
|
|
monitorInterval: time.Hour,
|
|
},
|
|
},
|
|
{
|
|
name: "valid-with-all",
|
|
args: args{
|
|
serverId: "test-server",
|
|
jobRepo: jobRepoFn,
|
|
},
|
|
opts: []Option{
|
|
WithRunJobsInterval(time.Hour),
|
|
WithMonitorInterval(2 * time.Hour),
|
|
},
|
|
want: args{
|
|
serverId: "test-server",
|
|
runInterval: time.Hour,
|
|
monitorInterval: 2 * time.Hour,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
got, err := New(ctx, tt.args.serverId, tt.args.jobRepo, tt.opts...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err)
|
|
assert.Equal(tt.wantErrMsg, err.Error())
|
|
assert.Nil(got)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
|
|
assert.Equal(tt.want.serverId, got.serverId)
|
|
assert.Equal(tt.want.runInterval, got.runJobsInterval)
|
|
assert.Equal(tt.want.monitorInterval, got.monitorInterval)
|
|
assert.NotNil(got.jobRepoFn)
|
|
assert.NotNil(got.runningJobs)
|
|
assert.NotNil(got.registeredJobs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScheduler_RegisterJob(t *testing.T) {
|
|
t.Parallel()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
iam.TestRepo(t, conn, wrapper)
|
|
|
|
sched := TestScheduler(t, conn, wrapper)
|
|
|
|
tests := []struct {
|
|
name string
|
|
job Job
|
|
wantErr bool
|
|
wantErrCode errors.Code
|
|
wantErrMsg string
|
|
}{
|
|
{
|
|
name: "nil-job",
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
wantErrMsg: "scheduler.(Scheduler).RegisterJob: scheduler.validateJob: missing job: parameter violation: error #100",
|
|
},
|
|
{
|
|
name: "missing-job-name",
|
|
job: testJob{},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
wantErrMsg: "scheduler.(Scheduler).RegisterJob: scheduler.validateJob: missing name: parameter violation: error #100",
|
|
},
|
|
{
|
|
name: "missing-job-description",
|
|
job: testJob{
|
|
name: "name",
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
wantErrMsg: "scheduler.(Scheduler).RegisterJob: scheduler.validateJob: missing description: parameter violation: error #100",
|
|
},
|
|
{
|
|
name: "valid",
|
|
job: testJob{
|
|
name: "valid",
|
|
description: "description",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
err := sched.RegisterJob(context.Background(), tt.job)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err)
|
|
assert.Equal(tt.wantErrMsg, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
|
|
j, ok := sched.registeredJobs.Load(tt.job.Name())
|
|
require.True(ok)
|
|
require.NotNil(j)
|
|
assert.Equal(tt.job, j)
|
|
|
|
// Verify job has been persisted
|
|
var dbJob job.Job
|
|
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []any{tt.job.Name()})
|
|
require.NoError(err)
|
|
require.NotNil(dbJob)
|
|
assert.Equal(tt.job.Name(), dbJob.Name)
|
|
assert.Equal(tt.job.Description(), dbJob.Description)
|
|
})
|
|
}
|
|
t.Run("multiple-same-job", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
tj := testJob{
|
|
name: "multiple-same-job",
|
|
description: "description",
|
|
}
|
|
|
|
// Job should not be registered
|
|
_, ok := sched.registeredJobs.Load(tj.name)
|
|
assert.False(ok)
|
|
|
|
err := sched.RegisterJob(context.Background(), tj)
|
|
require.NoError(err)
|
|
|
|
// Job should now be registered
|
|
_, ok = sched.registeredJobs.Load(tj.name)
|
|
assert.True(ok)
|
|
|
|
// Registering the same job should not return an error
|
|
err = sched.RegisterJob(context.Background(), tj)
|
|
require.NoError(err)
|
|
|
|
// Job should still be registered
|
|
_, ok = sched.registeredJobs.Load(tj.name)
|
|
assert.True(ok)
|
|
})
|
|
t.Run("multiple-schedulers-registering-same-job", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
tj := testJob{
|
|
name: "multiple-schedulers-registering-same-job",
|
|
description: "description",
|
|
}
|
|
err := sched.RegisterJob(context.Background(), tj)
|
|
require.NoError(err)
|
|
_, ok := sched.registeredJobs.Load(tj.name)
|
|
assert.True(ok)
|
|
|
|
sched1 := TestScheduler(t, conn, wrapper)
|
|
// Verify job is not registered on second scheduler
|
|
_, ok = sched1.registeredJobs.Load(tj.name)
|
|
assert.False(ok)
|
|
|
|
// Registering job on second scheduler should not return an error and should be registered
|
|
err = sched1.RegisterJob(context.Background(), tj)
|
|
require.NoError(err)
|
|
_, ok = sched.registeredJobs.Load(tj.name)
|
|
assert.True(ok)
|
|
})
|
|
}
|
|
|
|
func TestScheduler_UpdateJobNextRunInAtLeast(t *testing.T) {
|
|
t.Parallel()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
iam.TestRepo(t, conn, wrapper)
|
|
|
|
sched := TestScheduler(t, conn, wrapper)
|
|
|
|
t.Run("missing-job-name", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
err := sched.UpdateJobNextRunInAtLeast(context.Background(), "", time.Hour)
|
|
require.Error(err)
|
|
assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "Unexpected error %s", err)
|
|
assert.Equal("scheduler.(Scheduler).UpdateJobNextRunInAtLeast: missing name: parameter violation: error #100", err.Error())
|
|
})
|
|
t.Run("job-not-found", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
// Insert fake job to bypass registration check
|
|
sched.registeredJobs.Store("fake-job-name", nil)
|
|
|
|
err := sched.UpdateJobNextRunInAtLeast(context.Background(), "fake-job-name", time.Hour)
|
|
require.Error(err)
|
|
assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "Unexpected error %s", err)
|
|
assert.Equal("scheduler.(Scheduler).UpdateJobNextRunInAtLeast: job.(Repository).UpdateJobNextRunInAtLeast: db.DoTx: job.(Repository).UpdateJobNextRunInAtLeast: job \"fake-job-name\" does not exist: search issue: error #1100", err.Error())
|
|
})
|
|
t.Run("valid", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
tj := testJob{
|
|
name: "valid",
|
|
description: "description",
|
|
}
|
|
err := sched.RegisterJob(context.Background(), tj, WithNextRunIn(2*time.Hour))
|
|
require.NoError(err)
|
|
|
|
// get job from repo
|
|
var dbJob job.Job
|
|
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []any{tj.name})
|
|
require.NoError(err)
|
|
previousNextRun := dbJob.NextScheduledRun.AsTime()
|
|
|
|
err = sched.UpdateJobNextRunInAtLeast(context.Background(), tj.name, time.Hour)
|
|
require.NoError(err)
|
|
|
|
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []any{tj.name})
|
|
require.NoError(err)
|
|
// Verify job run time in repo is at least 1 hour before than the previous run time
|
|
assert.Equal(previousNextRun.Add(-1*time.Hour).Round(time.Minute), dbJob.NextScheduledRun.AsTime().Round(time.Minute))
|
|
})
|
|
|
|
t.Run("next-run-already-sooner", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
tj := testJob{
|
|
name: "next-run-already-sooner",
|
|
description: "description",
|
|
}
|
|
err := sched.RegisterJob(context.Background(), tj, WithNextRunIn(2*time.Hour))
|
|
require.NoError(err)
|
|
|
|
// get job from repo
|
|
var dbJob job.Job
|
|
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []any{tj.name})
|
|
require.NoError(err)
|
|
previousNextRun := dbJob.NextScheduledRun.AsTime()
|
|
|
|
err = sched.UpdateJobNextRunInAtLeast(context.Background(), tj.name, 4*time.Hour)
|
|
require.NoError(err)
|
|
|
|
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []any{tj.name})
|
|
require.NoError(err)
|
|
// Job should not have updated next run time, since its later than the already scheduled time
|
|
assert.Equal(previousNextRun, dbJob.NextScheduledRun.AsTime())
|
|
|
|
err = sched.UpdateJobNextRunInAtLeast(context.Background(), tj.name, time.Hour)
|
|
require.NoError(err)
|
|
|
|
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []any{tj.name})
|
|
require.NoError(err)
|
|
// Verify job run time in repo is at least 1 hour before than the previous run time
|
|
assert.Equal(previousNextRun.Add(-1*time.Hour).Round(time.Minute), dbJob.NextScheduledRun.AsTime().Round(time.Minute))
|
|
})
|
|
}
|
|
|
|
func TestScheduler_Start(t *testing.T) {
|
|
t.Parallel()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrapper := db.TestWrapper(t)
|
|
iam.TestRepo(t, conn, wrapper)
|
|
|
|
sched := TestScheduler(t, conn, wrapper)
|
|
|
|
tests := []struct {
|
|
name string
|
|
ctx context.Context
|
|
wg *sync.WaitGroup
|
|
wantErr bool
|
|
wantErrContains string
|
|
}{
|
|
{
|
|
name: "missing-ctx",
|
|
wg: &sync.WaitGroup{},
|
|
wantErr: true,
|
|
wantErrContains: "missing context",
|
|
},
|
|
{
|
|
name: "missing-waitgroup",
|
|
ctx: context.Background(),
|
|
wantErr: true,
|
|
wantErrContains: "missing wait group",
|
|
},
|
|
|
|
{
|
|
name: "valid",
|
|
ctx: context.Background(),
|
|
wg: &sync.WaitGroup{},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
err := sched.Start(tt.ctx, tt.wg)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
if tt.wantErrContains != "" {
|
|
assert.Contains(err.Error(), tt.wantErrContains)
|
|
}
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|