532 lines
15 KiB
Go
532 lines
15 KiB
Go
package storetest
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
platform "github.com/influxdata/influxdb"
|
|
pcontext "github.com/influxdata/influxdb/context"
|
|
"github.com/influxdata/influxdb/task/backend"
|
|
platformtesting "github.com/influxdata/influxdb/testing"
|
|
)
|
|
|
|
// MakeNewAuthorizationFunc is a function that creates a new authorization associated with a valid org and user.
|
|
// The permissions on the authorization should be allowed to do everything (see influxdb.OperPermissions).
|
|
type MakeNewAuthorizationFunc func(context.Context, *testing.T) *platform.Authorization
|
|
|
|
// CreateRunStoreFunc returns a new LogWriter and LogReader.
|
|
// If the writer and reader are associated with a backend that validates authorizations,
|
|
// it must return a valid MakeNewAuthorizationFunc; otherwise the returned MakeNewAuthorizationFunc may be nil,
|
|
// in which case the tests will use authorizations associated with a random org and user ID.
|
|
type CreateRunStoreFunc func(*testing.T) (backend.LogWriter, backend.LogReader, MakeNewAuthorizationFunc)
|
|
type DestroyRunStoreFunc func(*testing.T, backend.LogWriter, backend.LogReader)
|
|
|
|
func NewRunStoreTest(name string, crf CreateRunStoreFunc, drf DestroyRunStoreFunc) func(*testing.T) {
|
|
return func(t *testing.T) {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("UpdateRunState", func(t *testing.T) {
|
|
t.Parallel()
|
|
updateRunState(t, crf, drf)
|
|
})
|
|
t.Run("RunLog", func(t *testing.T) {
|
|
t.Parallel()
|
|
runLogTest(t, crf, drf)
|
|
})
|
|
t.Run("ListRuns", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping test in short mode.")
|
|
}
|
|
t.Parallel()
|
|
listRunsTest(t, crf, drf)
|
|
})
|
|
t.Run("FindRunByID", func(t *testing.T) {
|
|
t.Parallel()
|
|
findRunByIDTest(t, crf, drf)
|
|
})
|
|
t.Run("ListLogs", func(t *testing.T) {
|
|
t.Parallel()
|
|
listLogsTest(t, crf, drf)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func updateRunState(t *testing.T, crf CreateRunStoreFunc, drf DestroyRunStoreFunc) {
|
|
writer, reader, makeAuthz := crf(t)
|
|
defer drf(t, writer, reader)
|
|
|
|
now := time.Now().UTC()
|
|
|
|
task := &backend.StoreTask{
|
|
ID: platformtesting.MustIDBase16("ab01ab01ab01ab01"),
|
|
Org: platformtesting.MustIDBase16("ab01ab01ab01ab05"),
|
|
}
|
|
scheduledFor := now.Add(-3 * time.Second)
|
|
run := platform.Run{
|
|
ID: platformtesting.MustIDBase16("2c20766972747573"),
|
|
TaskID: task.ID,
|
|
Status: "started",
|
|
ScheduledFor: scheduledFor.Format(time.RFC3339),
|
|
}
|
|
rlb := backend.RunLogBase{
|
|
Task: task,
|
|
RunID: run.ID,
|
|
RunScheduledFor: scheduledFor.Unix(),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
ctx = pcontext.SetAuthorizer(ctx, makeNewAuthorization(ctx, t, makeAuthz))
|
|
|
|
startAt := now.Add(-2 * time.Second)
|
|
if err := writer.UpdateRunState(ctx, rlb, startAt, backend.RunStarted); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run.StartedAt = startAt.Format(time.RFC3339Nano)
|
|
run.Status = "started"
|
|
|
|
returnedRun, err := reader.FindRunByID(ctx, task.Org, run.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if diff := cmp.Diff(run, *returnedRun); diff != "" {
|
|
t.Fatalf("unexpected run found: -want/+got: %s", diff)
|
|
}
|
|
|
|
endAt := now.Add(-1 * time.Second)
|
|
if err := writer.UpdateRunState(ctx, rlb, endAt, backend.RunSuccess); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run.FinishedAt = endAt.Format(time.RFC3339Nano)
|
|
run.Status = "success"
|
|
|
|
returnedRun, err = reader.FindRunByID(ctx, task.Org, run.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if diff := cmp.Diff(run, *returnedRun); diff != "" {
|
|
t.Fatalf("unexpected run found: -want/+got: %s", diff)
|
|
}
|
|
|
|
now = time.Now().UTC()
|
|
|
|
// create a failed run
|
|
scheduledFor2 := now.Add(-2 * time.Second)
|
|
run2 := platform.Run{
|
|
ID: platformtesting.MustIDBase16("2c20766972747574"),
|
|
TaskID: task.ID,
|
|
Status: "started",
|
|
ScheduledFor: scheduledFor2.Format(time.RFC3339),
|
|
}
|
|
rlb2 := backend.RunLogBase{
|
|
Task: task,
|
|
RunID: run2.ID,
|
|
RunScheduledFor: scheduledFor2.Unix(),
|
|
}
|
|
|
|
startAt2 := now.Add(-1 * time.Second)
|
|
if err := writer.UpdateRunState(ctx, rlb2, startAt2, backend.RunStarted); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
endAt2 := now.Add(-1 * time.Millisecond)
|
|
if err := writer.UpdateRunState(ctx, rlb2, endAt2, backend.RunFail); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run2.StartedAt = startAt2.Format(time.RFC3339Nano)
|
|
run2.FinishedAt = endAt2.Format(time.RFC3339Nano)
|
|
run2.Status = "failed"
|
|
|
|
runs, err := reader.ListRuns(ctx, task.Org, platform.RunFilter{Task: task.ID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(runs) != 2 {
|
|
t.Fatalf("expected 2 runs, got: %d", len(runs))
|
|
}
|
|
|
|
if diff := cmp.Diff(runs, []*platform.Run{&run, &run2}); diff != "" {
|
|
for i, r := range runs {
|
|
t.Logf("returned run[%d]: %+#v", i, *r)
|
|
}
|
|
t.Fatalf("unexpected run2 found: -want/+got: %s", diff)
|
|
}
|
|
}
|
|
|
|
func runLogTest(t *testing.T, crf CreateRunStoreFunc, drf DestroyRunStoreFunc) {
|
|
writer, reader, makeAuthz := crf(t)
|
|
defer drf(t, writer, reader)
|
|
|
|
task := &backend.StoreTask{
|
|
ID: platformtesting.MustIDBase16("ab01ab01ab01ab01"),
|
|
Org: platformtesting.MustIDBase16("ab01ab01ab01ab05"),
|
|
}
|
|
|
|
sf := time.Now().UTC()
|
|
sa := sf.Add(-10 * time.Second)
|
|
run := platform.Run{
|
|
ID: platformtesting.MustIDBase16("2c20766972747573"),
|
|
TaskID: task.ID,
|
|
Status: "started",
|
|
ScheduledFor: sf.Format(time.RFC3339),
|
|
StartedAt: sa.Format(time.RFC3339Nano),
|
|
}
|
|
rlb := backend.RunLogBase{
|
|
Task: task,
|
|
RunID: run.ID,
|
|
RunScheduledFor: sf.Unix(),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
ctx = pcontext.SetAuthorizer(ctx, makeNewAuthorization(ctx, t, makeAuthz))
|
|
|
|
if err := writer.UpdateRunState(ctx, rlb, sa, backend.RunStarted); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := writer.AddRunLog(ctx, rlb, sa.Add(time.Second), "first"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writer.AddRunLog(ctx, rlb, sa.Add(2*time.Second), "second"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writer.AddRunLog(ctx, rlb, sa.Add(3*time.Second), "third"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
run.Log = []platform.Log{
|
|
platform.Log{Time: sa.Add(time.Second).Format(time.RFC3339Nano), Message: "first"},
|
|
platform.Log{Time: sa.Add(2 * time.Second).Format(time.RFC3339Nano), Message: "second"},
|
|
platform.Log{Time: sa.Add(3 * time.Second).Format(time.RFC3339Nano), Message: "third"},
|
|
}
|
|
returnedRun, err := reader.FindRunByID(ctx, task.Org, run.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if diff := cmp.Diff(run, *returnedRun); diff != "" {
|
|
t.Fatalf("unexpected run found: -want/+got: %s", diff)
|
|
}
|
|
}
|
|
|
|
func listRunsTest(t *testing.T, crf CreateRunStoreFunc, drf DestroyRunStoreFunc) {
|
|
writer, reader, makeAuthz := crf(t)
|
|
defer drf(t, writer, reader)
|
|
|
|
task := &backend.StoreTask{
|
|
ID: platformtesting.MustIDBase16("ab01ab01ab01ab01"),
|
|
Org: platformtesting.MustIDBase16("ab01ab01ab01ab05"),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
ctx = pcontext.SetAuthorizer(ctx, makeNewAuthorization(ctx, t, makeAuthz))
|
|
|
|
{
|
|
r, err := reader.ListRuns(ctx, task.ID, platform.RunFilter{Task: task.ID})
|
|
if err != nil {
|
|
// TODO(lh): We may get an error here in the future when the system is more aggressive at checking orgID's
|
|
t.Fatalf("got an error with bad orgID when we should have returned a empty list: %v", err)
|
|
}
|
|
if len(r) != 0 {
|
|
t.Fatalf("expected 0 runs, got: %d", len(r))
|
|
}
|
|
}
|
|
{
|
|
r, err := reader.ListRuns(ctx, task.Org, platform.RunFilter{Task: task.Org})
|
|
if err != nil {
|
|
t.Fatalf("got an error with bad taskID when we should have returned a empty list: %v", err)
|
|
}
|
|
if len(r) != 0 {
|
|
t.Fatalf("expected 0 runs, got: %d", len(r))
|
|
}
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
const nRuns = 150
|
|
runs := make([]platform.Run, nRuns)
|
|
for i := 0; i < len(runs); i++ {
|
|
// Scheduled for times ascending with IDs.
|
|
scheduledFor := now.Add(time.Duration(-2*(nRuns-i)) * time.Second)
|
|
id := platform.ID(i + 1)
|
|
runs[i] = platform.Run{
|
|
ID: id,
|
|
Status: "started",
|
|
ScheduledFor: scheduledFor.Format(time.RFC3339),
|
|
}
|
|
rlb := backend.RunLogBase{
|
|
Task: task,
|
|
RunID: runs[i].ID,
|
|
RunScheduledFor: scheduledFor.Unix(),
|
|
}
|
|
|
|
err := writer.UpdateRunState(ctx, rlb, scheduledFor.Add(time.Second), backend.RunStarted)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if _, err := reader.ListRuns(ctx, task.Org, platform.RunFilter{}); err == nil {
|
|
t.Fatal("failed to error with invalid task ID")
|
|
}
|
|
{
|
|
r, err := reader.ListRuns(ctx, 9999999, platform.RunFilter{Task: task.ID})
|
|
if err != nil {
|
|
t.Fatalf("got an error with a bad orgID when we should have returned a empty list: %v", err)
|
|
}
|
|
if len(r) != 0 {
|
|
t.Fatalf("expected 0 runs, got: %d", len(r))
|
|
}
|
|
}
|
|
|
|
listRuns, err := reader.ListRuns(ctx, task.Org, platform.RunFilter{
|
|
Task: task.ID,
|
|
Limit: 2 * nRuns,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(listRuns) != len(runs) {
|
|
t.Fatalf("retrieved: %d, expected: %d", len(listRuns), len(runs))
|
|
}
|
|
|
|
const afterIDIdx = 20
|
|
listRuns, err = reader.ListRuns(ctx, task.Org, platform.RunFilter{
|
|
Task: task.ID,
|
|
After: &runs[afterIDIdx].ID,
|
|
Limit: 2 * nRuns,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(listRuns) != len(runs)-(afterIDIdx+1) {
|
|
t.Fatalf("retrieved: %d, expected: %d", len(listRuns), len(runs)-(afterIDIdx+1))
|
|
}
|
|
|
|
listRuns, err = reader.ListRuns(ctx, task.Org, platform.RunFilter{
|
|
Task: task.ID,
|
|
Limit: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(listRuns) != 30 {
|
|
t.Fatalf("retrieved: %d, expected: %d", len(listRuns), 30)
|
|
}
|
|
|
|
const afterTimeIdx = 34
|
|
scheduledFor, _ := time.Parse(time.RFC3339, runs[afterTimeIdx].ScheduledFor)
|
|
listRuns, err = reader.ListRuns(ctx, task.Org, platform.RunFilter{
|
|
Task: task.ID,
|
|
AfterTime: scheduledFor.Format(time.RFC3339),
|
|
Limit: 2 * nRuns,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(listRuns) != len(runs)-(afterTimeIdx+1) {
|
|
t.Fatalf("retrieved: %d, expected: %d", len(listRuns), len(runs)-(afterTimeIdx+1))
|
|
}
|
|
|
|
const beforeTimeIdx = 34
|
|
scheduledFor, _ = time.Parse(time.RFC3339, runs[beforeTimeIdx].ScheduledFor)
|
|
listRuns, err = reader.ListRuns(ctx, task.Org, platform.RunFilter{
|
|
Task: task.ID,
|
|
BeforeTime: scheduledFor.Add(time.Millisecond).Format(time.RFC3339),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(listRuns) != beforeTimeIdx {
|
|
t.Fatalf("retrieved: %d, expected: %d", len(listRuns), beforeTimeIdx)
|
|
}
|
|
|
|
// add a run and now list again but this time with a requested at
|
|
scheduledFor = now.Add(time.Duration(-2*(nRuns-len(runs))) * time.Second)
|
|
run := platform.Run{
|
|
ID: platform.ID(len(runs) + 1),
|
|
Status: "started",
|
|
ScheduledFor: scheduledFor.Format(time.RFC3339),
|
|
RequestedAt: scheduledFor.Format(time.RFC3339),
|
|
}
|
|
runs = append(runs, run)
|
|
rlb := backend.RunLogBase{
|
|
Task: task,
|
|
RunID: run.ID,
|
|
RunScheduledFor: scheduledFor.Unix(),
|
|
RequestedAt: scheduledFor.Unix(),
|
|
}
|
|
|
|
if err := writer.UpdateRunState(ctx, rlb, scheduledFor.Add(time.Second), backend.RunStarted); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(time.Second)
|
|
listRuns, err = reader.ListRuns(ctx, task.Org, platform.RunFilter{
|
|
Task: task.ID,
|
|
Limit: 2 * nRuns,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(listRuns) != len(runs) {
|
|
t.Fatalf("retrieved: %d, expected: %d", len(listRuns), len(runs))
|
|
}
|
|
}
|
|
|
|
func findRunByIDTest(t *testing.T, crf CreateRunStoreFunc, drf DestroyRunStoreFunc) {
|
|
writer, reader, makeAuthz := crf(t)
|
|
defer drf(t, writer, reader)
|
|
|
|
if _, err := reader.FindRunByID(context.Background(), platform.InvalidID(), platform.InvalidID()); err == nil {
|
|
t.Fatal("failed to error with bad id")
|
|
}
|
|
|
|
task := &backend.StoreTask{
|
|
ID: platformtesting.MustIDBase16("ab01ab01ab01ab01"),
|
|
Org: platformtesting.MustIDBase16("ab01ab01ab01ab05"),
|
|
}
|
|
sf := time.Now().UTC().Add(-10 * time.Second)
|
|
sa := sf.Add(time.Second)
|
|
|
|
run := platform.Run{
|
|
ID: platformtesting.MustIDBase16("2c20766972747573"),
|
|
TaskID: task.ID,
|
|
Status: "started",
|
|
ScheduledFor: sf.Format(time.RFC3339),
|
|
StartedAt: sa.Format(time.RFC3339Nano),
|
|
}
|
|
rlb := backend.RunLogBase{
|
|
Task: task,
|
|
RunID: run.ID,
|
|
RunScheduledFor: sf.Unix(),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
ctx = pcontext.SetAuthorizer(ctx, makeNewAuthorization(ctx, t, makeAuthz))
|
|
if err := writer.UpdateRunState(ctx, rlb, sa, backend.RunStarted); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
returnedRun, err := reader.FindRunByID(ctx, task.Org, run.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(run, *returnedRun) {
|
|
t.Fatalf("expected:\n%#v, got: \n%#v", run, *returnedRun)
|
|
}
|
|
|
|
returnedRun.Log = []platform.Log{platform.Log{Message: "cows"}}
|
|
|
|
rr2, err := reader.FindRunByID(ctx, task.Org, run.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if reflect.DeepEqual(returnedRun, rr2) {
|
|
t.Fatalf("updateing returned run modified RunStore data")
|
|
}
|
|
|
|
_, err = reader.FindRunByID(ctx, task.Org, 0xccc)
|
|
if err != backend.ErrRunNotFound {
|
|
t.Fatalf("expected finding run with invalid ID to return %v, got %v", backend.ErrRunNotFound, err)
|
|
}
|
|
}
|
|
|
|
func listLogsTest(t *testing.T, crf CreateRunStoreFunc, drf DestroyRunStoreFunc) {
|
|
writer, reader, makeAuthz := crf(t)
|
|
defer drf(t, writer, reader)
|
|
|
|
task := &backend.StoreTask{
|
|
ID: platformtesting.MustIDBase16("ab01ab01ab01ab01"),
|
|
Org: platformtesting.MustIDBase16("ab01ab01ab01ab05"),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
ctx = pcontext.SetAuthorizer(ctx, makeNewAuthorization(ctx, t, makeAuthz))
|
|
|
|
if _, err := reader.ListLogs(ctx, task.Org, platform.LogFilter{}); err == nil {
|
|
t.Fatalf("expected error when task ID missing, but got nil")
|
|
}
|
|
r, err := reader.ListLogs(ctx, 9999999, platform.LogFilter{Task: task.ID})
|
|
if err != nil {
|
|
t.Fatalf("with bad org ID, expected no error: %v", err)
|
|
}
|
|
if len(r) != 0 {
|
|
t.Fatalf("with bad org id expected no runs, got: %d", len(r))
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
const nRuns = 20
|
|
runs := make([]platform.Run, nRuns)
|
|
for i := 0; i < len(runs); i++ {
|
|
sf := now.Add(time.Duration(i-nRuns) * time.Second)
|
|
id := platform.ID(i + 1)
|
|
runs[i] = platform.Run{
|
|
ID: id,
|
|
Status: "started",
|
|
ScheduledFor: sf.UTC().Format(time.RFC3339),
|
|
}
|
|
rlb := backend.RunLogBase{
|
|
Task: task,
|
|
RunID: runs[i].ID,
|
|
RunScheduledFor: sf.Unix(),
|
|
}
|
|
|
|
err := writer.UpdateRunState(ctx, rlb, sf.Add(time.Millisecond), backend.RunStarted)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
writer.AddRunLog(ctx, rlb, sf.Add(2*time.Millisecond), fmt.Sprintf("log%d", i))
|
|
}
|
|
|
|
const targetRun = 4
|
|
logs, err := reader.ListLogs(ctx, task.Org, platform.LogFilter{Task: task.ID, Run: &runs[targetRun].ID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(logs) != 1 {
|
|
t.Fatalf("expected 1 log, got %d", len(logs))
|
|
}
|
|
|
|
fmtTimelog := now.Add(time.Duration(targetRun-nRuns)*time.Second + 2*time.Millisecond).Format(time.RFC3339Nano)
|
|
if logs[0].Time != fmtTimelog {
|
|
t.Fatalf("expected: %q, got: %q", fmtTimelog, logs[0].Time)
|
|
}
|
|
if logs[0].Message != "log4" {
|
|
t.Fatalf("expected: %q, got: %q", "log4", logs[0].Message)
|
|
}
|
|
|
|
logs, err = reader.ListLogs(ctx, task.Org, platform.LogFilter{Task: task.ID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(logs) != len(runs) {
|
|
t.Fatal("not all logs retrieved")
|
|
}
|
|
}
|
|
|
|
func makeNewAuthorization(ctx context.Context, t *testing.T, makeAuthz MakeNewAuthorizationFunc) *platform.Authorization {
|
|
if makeAuthz != nil {
|
|
return makeAuthz(ctx, t)
|
|
}
|
|
|
|
return &platform.Authorization{
|
|
ID: platformtesting.MustIDBase16("ab01ab01ab01ab01"),
|
|
UserID: platformtesting.MustIDBase16("ab01ab01ab01ab01"),
|
|
OrgID: platformtesting.MustIDBase16("ab01ab01ab01ab05"),
|
|
Permissions: platform.OperPermissions(),
|
|
}
|
|
}
|