|
|
|
|
@ -4,6 +4,9 @@ import ( |
|
|
|
|
"bytes" |
|
|
|
|
"context" |
|
|
|
|
"encoding/json" |
|
|
|
|
"fmt" |
|
|
|
|
"io" |
|
|
|
|
"path/filepath" |
|
|
|
|
"testing" |
|
|
|
|
|
|
|
|
|
"github.com/prometheus/common/model" |
|
|
|
|
@ -18,17 +21,20 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
now := model.Now() |
|
|
|
|
|
|
|
|
|
for _, tc := range []struct { |
|
|
|
|
name string |
|
|
|
|
setupManifest func(client client.ObjectClient) |
|
|
|
|
expectedJobs []grpc.Job |
|
|
|
|
name string |
|
|
|
|
setupManifest func(client client.ObjectClient) string |
|
|
|
|
expectedJobs []grpc.Job |
|
|
|
|
expectedIndexUpdates map[string][]byte |
|
|
|
|
}{ |
|
|
|
|
{ |
|
|
|
|
name: "no manifests in storage", |
|
|
|
|
setupManifest: func(_ client.ObjectClient) {}, |
|
|
|
|
name: "no manifests in storage", |
|
|
|
|
setupManifest: func(_ client.ObjectClient) string { |
|
|
|
|
return "" |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "one manifest in storage with less than maxChunksPerJob", |
|
|
|
|
setupManifest: func(client client.ObjectClient) { |
|
|
|
|
setupManifest: func(client client.ObjectClient) string { |
|
|
|
|
deleteRequestBatch := newDeleteRequestBatch(nil) |
|
|
|
|
deleteRequestBatch.addDeleteRequest(&DeleteRequest{ |
|
|
|
|
UserID: user1, |
|
|
|
|
@ -46,6 +52,8 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
})) |
|
|
|
|
|
|
|
|
|
require.NoError(t, manifestBuilder.Finish(context.Background())) |
|
|
|
|
|
|
|
|
|
return manifestBuilder.path() |
|
|
|
|
}, |
|
|
|
|
expectedJobs: []grpc.Job{ |
|
|
|
|
{ |
|
|
|
|
@ -66,10 +74,13 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
}), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
expectedIndexUpdates: map[string][]byte{ |
|
|
|
|
fmt.Sprintf("%d%s", 0, indexUpdatesFilenameSuffix): buildIndexUpdates(t, table1, 0, 1), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "one manifest in storage with more than maxChunksPerJob", |
|
|
|
|
setupManifest: func(client client.ObjectClient) { |
|
|
|
|
setupManifest: func(client client.ObjectClient) string { |
|
|
|
|
deleteRequestBatch := newDeleteRequestBatch(nil) |
|
|
|
|
deleteRequestBatch.addDeleteRequest(&DeleteRequest{ |
|
|
|
|
UserID: user1, |
|
|
|
|
@ -87,6 +98,7 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
})) |
|
|
|
|
|
|
|
|
|
require.NoError(t, manifestBuilder.Finish(context.Background())) |
|
|
|
|
return manifestBuilder.path() |
|
|
|
|
}, |
|
|
|
|
expectedJobs: []grpc.Job{ |
|
|
|
|
{ |
|
|
|
|
@ -124,10 +136,13 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
}), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
expectedIndexUpdates: map[string][]byte{ |
|
|
|
|
fmt.Sprintf("%d%s", 0, indexUpdatesFilenameSuffix): buildIndexUpdates(t, table1, 0, 2), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "one manifest in storage with multiple groups", |
|
|
|
|
setupManifest: func(client client.ObjectClient) { |
|
|
|
|
setupManifest: func(client client.ObjectClient) string { |
|
|
|
|
deleteRequestBatch := newDeleteRequestBatch(nil) |
|
|
|
|
deleteRequestBatch.addDeleteRequest(&DeleteRequest{ |
|
|
|
|
UserID: user1, |
|
|
|
|
@ -153,6 +168,7 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
})) |
|
|
|
|
|
|
|
|
|
require.NoError(t, manifestBuilder.Finish(context.Background())) |
|
|
|
|
return manifestBuilder.path() |
|
|
|
|
}, |
|
|
|
|
expectedJobs: []grpc.Job{ |
|
|
|
|
{ |
|
|
|
|
@ -199,10 +215,13 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
}), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
expectedIndexUpdates: map[string][]byte{ |
|
|
|
|
fmt.Sprintf("%d%s", 0, indexUpdatesFilenameSuffix): buildIndexUpdates(t, table1, 0, 2), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "one manifest in storage with multiple segments due to multiple tables", |
|
|
|
|
setupManifest: func(client client.ObjectClient) { |
|
|
|
|
setupManifest: func(client client.ObjectClient) string { |
|
|
|
|
deleteRequestBatch := newDeleteRequestBatch(nil) |
|
|
|
|
deleteRequestBatch.addDeleteRequest(&DeleteRequest{ |
|
|
|
|
UserID: user1, |
|
|
|
|
@ -226,6 +245,7 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
})) |
|
|
|
|
|
|
|
|
|
require.NoError(t, manifestBuilder.Finish(context.Background())) |
|
|
|
|
return manifestBuilder.path() |
|
|
|
|
}, |
|
|
|
|
expectedJobs: []grpc.Job{ |
|
|
|
|
{ |
|
|
|
|
@ -263,6 +283,10 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
}), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
expectedIndexUpdates: map[string][]byte{ |
|
|
|
|
fmt.Sprintf("%d%s", 0, indexUpdatesFilenameSuffix): buildIndexUpdates(t, table1, 0, 1), |
|
|
|
|
fmt.Sprintf("%d%s", 1, indexUpdatesFilenameSuffix): buildIndexUpdates(t, table2, 1, 1), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
} { |
|
|
|
|
t.Run(tc.name, func(t *testing.T) { |
|
|
|
|
@ -270,19 +294,23 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
Directory: t.TempDir(), |
|
|
|
|
}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
tc.setupManifest(objectClient) |
|
|
|
|
manifestPath := tc.setupManifest(objectClient) |
|
|
|
|
|
|
|
|
|
builder := NewJobBuilder(objectClient) |
|
|
|
|
jobsChan := make(chan *grpc.Job) |
|
|
|
|
|
|
|
|
|
var jobsBuilt []grpc.Job |
|
|
|
|
go func() { |
|
|
|
|
cnt := 0 |
|
|
|
|
for job := range jobsChan { |
|
|
|
|
jobsBuilt = append(jobsBuilt, *job) |
|
|
|
|
builder.OnJobResponse(&grpc.JobResult{ |
|
|
|
|
err := builder.OnJobResponse(&grpc.JobResult{ |
|
|
|
|
JobId: job.Id, |
|
|
|
|
JobType: job.Type, |
|
|
|
|
Result: mustMarshal(t, buildDeletionJobResult(cnt)), |
|
|
|
|
}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
cnt++ |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
@ -291,6 +319,17 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
|
|
|
|
|
require.Equal(t, len(tc.expectedJobs), len(jobsBuilt)) |
|
|
|
|
require.Equal(t, tc.expectedJobs, jobsBuilt) |
|
|
|
|
|
|
|
|
|
for filename, expectedIndexUpdate := range tc.expectedIndexUpdates { |
|
|
|
|
readCloser, _, err := objectClient.GetObject(context.Background(), filepath.Join(manifestPath, filename)) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
indexUpdateJSON, err := io.ReadAll(readCloser) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
require.NoError(t, readCloser.Close()) |
|
|
|
|
|
|
|
|
|
require.Equal(t, expectedIndexUpdate, indexUpdateJSON) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -298,10 +337,12 @@ func TestJobBuilder_buildJobs(t *testing.T) { |
|
|
|
|
func TestJobBuilder_ProcessManifest(t *testing.T) { |
|
|
|
|
for _, tc := range []struct { |
|
|
|
|
name string |
|
|
|
|
jobResult []byte |
|
|
|
|
jobProcessingError string |
|
|
|
|
}{ |
|
|
|
|
{ |
|
|
|
|
name: "all jobs succeeded", |
|
|
|
|
name: "all jobs succeeded", |
|
|
|
|
jobResult: []byte(`{}`), |
|
|
|
|
}, { |
|
|
|
|
name: "job failure should fail the manifest processing", |
|
|
|
|
jobProcessingError: "job processing failed", |
|
|
|
|
@ -339,17 +380,19 @@ func TestJobBuilder_ProcessManifest(t *testing.T) { |
|
|
|
|
} |
|
|
|
|
segmentData, err := json.Marshal(segment) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
err = objectClient.PutObject(context.Background(), "test-manifest/1.json", bytes.NewReader(segmentData)) |
|
|
|
|
err = objectClient.PutObject(context.Background(), "test-manifest/0.json", bytes.NewReader(segmentData)) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
jobsChan := make(chan *grpc.Job) |
|
|
|
|
go func() { |
|
|
|
|
for job := range jobsChan { |
|
|
|
|
builder.OnJobResponse(&grpc.JobResult{ |
|
|
|
|
err := builder.OnJobResponse(&grpc.JobResult{ |
|
|
|
|
JobId: job.Id, |
|
|
|
|
JobType: job.Type, |
|
|
|
|
Result: tc.jobResult, |
|
|
|
|
Error: tc.jobProcessingError, |
|
|
|
|
}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
@ -371,3 +414,37 @@ func mustMarshalPayload(job *deletionJob) []byte { |
|
|
|
|
|
|
|
|
|
return payload |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func buildDeletionJobResult(jobCounter int) JobResult { |
|
|
|
|
deletionJobResult := JobResult{ |
|
|
|
|
ChunksToDelete: []string{fmt.Sprintf("%d-d", jobCounter)}, |
|
|
|
|
ChunksToDeIndex: []string{fmt.Sprintf("%d-i", jobCounter)}, |
|
|
|
|
ChunksToIndex: []Chunk{ |
|
|
|
|
{ |
|
|
|
|
From: model.Time(jobCounter), |
|
|
|
|
Through: model.Time(jobCounter), |
|
|
|
|
Fingerprint: uint64(jobCounter), |
|
|
|
|
Checksum: uint32(jobCounter), |
|
|
|
|
KB: uint32(jobCounter), |
|
|
|
|
Entries: uint32(jobCounter), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return deletionJobResult |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func buildIndexUpdates(t *testing.T, tableName string, jobIndexStart, totalJobs int) []byte { |
|
|
|
|
indexUpdates := indexUpdates{ |
|
|
|
|
TableName: tableName, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for i := 0; i < totalJobs; i++ { |
|
|
|
|
indexUpdates.addUpdates(buildDeletionJobResult(jobIndexStart + i)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
indexUpdatesJSON, err := indexUpdates.encode() |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
return indexUpdatesJSON |
|
|
|
|
} |
|
|
|
|
|