| package container |
| |
| import ( |
| "context" |
| "math/rand" |
| "os" |
| "path/filepath" |
| "testing" |
| |
| cerrdefs "github.com/containerd/errdefs" |
| "github.com/docker/docker/api/types/container" |
| "github.com/docker/docker/pkg/stringid" |
| "github.com/google/uuid" |
| "gotest.tools/v3/assert" |
| is "gotest.tools/v3/assert/cmp" |
| ) |
| |
| func newContainer(t *testing.T, root string) *Container { |
| t.Helper() |
| id := uuid.New().String() |
| cRoot := filepath.Join(root, id) |
| assert.NilError(t, os.Mkdir(cRoot, 0o755)) |
| return NewBaseContainer(id, cRoot) |
| } |
| |
| func TestViewSaveDelete(t *testing.T) { |
| db, err := NewViewDB() |
| assert.NilError(t, err) |
| |
| tmpDir := t.TempDir() |
| c := newContainer(t, tmpDir) |
| assert.NilError(t, c.CheckpointTo(context.Background(), db)) |
| assert.NilError(t, db.Delete(c)) |
| } |
| |
| func TestViewAll(t *testing.T) { |
| db, err := NewViewDB() |
| assert.NilError(t, err) |
| |
| tmpDir := t.TempDir() |
| one := newContainer(t, tmpDir) |
| two := newContainer(t, tmpDir) |
| |
| one.Pid = 10 |
| assert.NilError(t, one.CheckpointTo(context.Background(), db)) |
| |
| two.Pid = 20 |
| assert.NilError(t, two.CheckpointTo(context.Background(), db)) |
| |
| all, err := db.Snapshot().All() |
| assert.NilError(t, err) |
| assert.Assert(t, is.Len(all, 2)) |
| |
| byID := make(map[string]int) |
| for _, c := range all { |
| byID[c.ID] = c.Pid |
| } |
| expected := map[string]int{ |
| one.ID: one.Pid, |
| two.ID: two.Pid, |
| } |
| assert.DeepEqual(t, expected, byID) |
| } |
| |
| func TestViewGet(t *testing.T) { |
| db, err := NewViewDB() |
| assert.NilError(t, err) |
| |
| tmpDir := t.TempDir() |
| one := newContainer(t, tmpDir) |
| |
| const imgID = "some-image-123" |
| one.ImageID = imgID |
| |
| assert.NilError(t, one.CheckpointTo(context.Background(), db)) |
| s, err := db.Snapshot().Get(one.ID) |
| assert.NilError(t, err) |
| assert.Equal(t, s.ID, one.ID) |
| } |
| |
| func TestNames(t *testing.T) { |
| db, err := NewViewDB() |
| assert.NilError(t, err) |
| |
| assert.Check(t, db.ReserveName("name1", "containerid1")) |
| assert.Check(t, db.ReserveName("name1", "containerid1")) // idempotent |
| assert.Check(t, db.ReserveName("name2", "containerid2")) |
| |
| err = db.ReserveName("name2", "containerid3") |
| assert.Check(t, is.ErrorType(err, cerrdefs.IsConflict)) |
| assert.Check(t, is.Error(err, "name is reserved")) |
| |
| // Releasing a name allows the name to point to something else later. |
| assert.Check(t, db.ReleaseName("name2")) |
| assert.Check(t, db.ReserveName("name2", "containerid3")) |
| |
| view := db.Snapshot() |
| |
| id, err := view.GetID("name1") |
| assert.Check(t, err) |
| assert.Check(t, is.Equal("containerid1", id)) |
| |
| id, err = view.GetID("name2") |
| assert.Check(t, err) |
| assert.Check(t, is.Equal("containerid3", id)) |
| |
| _, err = view.GetID("notreserved") |
| assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) |
| assert.Check(t, is.Error(err, "name is not reserved")) |
| |
| // Releasing and re-reserving a name doesn't affect the snapshot. |
| assert.Check(t, db.ReleaseName("name2")) |
| assert.Check(t, db.ReserveName("name2", "containerid4")) |
| |
| id, err = view.GetID("name1") |
| assert.Check(t, err) |
| assert.Check(t, is.Equal("containerid1", id)) |
| |
| id, err = view.GetID("name2") |
| assert.Check(t, err) |
| assert.Check(t, is.Equal("containerid3", id)) |
| |
| // GetAllNames |
| assert.Check(t, is.DeepEqual(map[string][]string{"containerid1": {"name1"}, "containerid3": {"name2"}}, view.GetAllNames())) |
| |
| assert.Check(t, db.ReserveName("name3", "containerid1")) |
| assert.Check(t, db.ReserveName("name4", "containerid1")) |
| |
| view = db.Snapshot() |
| assert.Check(t, is.DeepEqual(map[string][]string{"containerid1": {"name1", "name3", "name4"}, "containerid4": {"name2"}}, view.GetAllNames())) |
| |
| // Release containerid1's names with Delete even though no container exists |
| assert.Check(t, db.Delete(&Container{ID: "containerid1"})) |
| |
| // Reusing one of those names should work |
| assert.Check(t, db.ReserveName("name1", "containerid4")) |
| view = db.Snapshot() |
| assert.Check(t, is.DeepEqual(map[string][]string{"containerid4": {"name1", "name2"}}, view.GetAllNames())) |
| } |
| |
| // Test case for GitHub issue 35920 |
| func TestViewWithHealthCheck(t *testing.T) { |
| db, err := NewViewDB() |
| assert.NilError(t, err) |
| |
| tmpDir := t.TempDir() |
| one := newContainer(t, tmpDir) |
| |
| one.Health = &Health{ |
| Health: container.Health{ |
| Status: container.Starting, |
| }, |
| } |
| assert.NilError(t, one.CheckpointTo(context.Background(), db)) |
| s, err := db.Snapshot().Get(one.ID) |
| assert.NilError(t, err) |
| assert.Equal(t, s.Health, container.Starting) |
| } |
| |
| func TestTruncIndex(t *testing.T) { |
| db, err := NewViewDB() |
| assert.NilError(t, err) |
| |
| // Get on an empty index |
| _, err = db.GetByPrefix("foobar") |
| assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) |
| |
| // Add an id |
| const id = "99b36c2c326ccc11e726eee6ee78a0baf166ef96" |
| assert.NilError(t, db.Save(&Container{ID: id})) |
| |
| type testacase struct { |
| name string |
| input string |
| expectedResult string |
| expectError bool |
| } |
| |
| for _, tc := range []testacase{ |
| { |
| name: "Get a non-existing id", |
| input: "abracadabra", |
| expectError: true, |
| }, |
| { |
| name: "Get an empty id", |
| input: "", |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "Get the exact id", |
| input: id, |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "The first letter should match", |
| input: id[:1], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "The first half should match", |
| input: id[:len(id)/2], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "The second half should NOT match", |
| input: id[len(id)/2:], |
| expectError: true, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| assertIndexGet(t, db, tc.input, tc.expectedResult, tc.expectError) |
| }) |
| } |
| |
| // Add an id with a prefix overlapping with the previous one |
| id2 := id[:6] + "blabla" |
| assert.NilError(t, db.Save(&Container{ID: id2})) |
| |
| for _, tc := range []testacase{ |
| { |
| name: "id should work", |
| input: id, |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "id2 should work", |
| input: id2, |
| expectedResult: id2, |
| expectError: false, |
| }, |
| { |
| name: "6 characters should conflict", |
| input: id[:6], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "4 characters should conflict", |
| input: id[:4], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "1 character should conflict", |
| input: id[:6], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "7 characters of id should not conflict", |
| input: id[:7], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "7 characters of id2 should not conflict", |
| input: id2[:7], |
| expectedResult: id2, |
| expectError: false, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| assertIndexGet(t, db, tc.input, tc.expectedResult, tc.expectError) |
| }) |
| } |
| |
| // Deleting id2 should remove conflicts |
| assert.NilError(t, db.Delete(&Container{ID: id2})) |
| |
| for _, tc := range []testacase{ |
| { |
| name: "id2 should no longer work", |
| input: id2, |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "7 characters id2 should no longer work", |
| input: id2[:7], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "11 characters id2 should no longer work", |
| input: id2[:11], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "conflicts between id[:6] and id2 should be gone", |
| input: id[:6], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "conflicts between id[:4] and id2 should be gone", |
| input: id[:4], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "conflicts between id[:1] and id2 should be gone", |
| input: id[:1], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "non-conflicting 7 character substrings should still not conflict", |
| input: id[:7], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "non-conflicting 15 character substrings should still not conflict", |
| input: id[:15], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "non-conflicting substrings should still not conflict", |
| input: id, |
| expectedResult: id, |
| expectError: false, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| assertIndexGet(t, db, tc.input, tc.expectedResult, tc.expectError) |
| }) |
| } |
| } |
| |
| func assertIndexGet(t *testing.T, snapshot *ViewDB, input, expectedResult string, expectError bool) { |
| if result, err := snapshot.GetByPrefix(input); err != nil && !expectError { |
| t.Fatalf("Unexpected error getting '%s': %s", input, err) |
| } else if err == nil && expectError { |
| t.Fatalf("Getting '%s' should return an error, not '%s'", input, result) |
| } else if result != expectedResult { |
| t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult) |
| } |
| } |
| |
| func BenchmarkDBAdd100(b *testing.B) { |
| var testSet []string |
| for i := 0; i < 100; i++ { |
| testSet = append(testSet, stringid.GenerateRandomID()) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| db, err := NewViewDB() |
| if err != nil { |
| b.Fatal(err) |
| } |
| for _, id := range testSet { |
| if err := db.Save(&Container{ID: id}); err != nil { |
| b.Fatal(err) |
| } |
| } |
| } |
| } |
| |
| func BenchmarkDBGetByPrefix100(b *testing.B) { |
| var testSet []string |
| var testKeys []string |
| for i := 0; i < 100; i++ { |
| testSet = append(testSet, stringid.GenerateRandomID()) |
| } |
| db, err := NewViewDB() |
| if err != nil { |
| b.Fatal(err) |
| } |
| for _, id := range testSet { |
| if err := db.Save(&Container{ID: id}); err != nil { |
| b.Fatal(err) |
| } |
| l := rand.Intn(12) + 12 |
| testKeys = append(testKeys, id[:l]) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| for _, id := range testKeys { |
| if res, err := db.GetByPrefix(id); err != nil { |
| b.Fatal(res, err) |
| } |
| } |
| } |
| } |
| |
| func BenchmarkDBGetByPrefix250(b *testing.B) { |
| var testSet []string |
| var testKeys []string |
| for i := 0; i < 250; i++ { |
| testSet = append(testSet, stringid.GenerateRandomID()) |
| } |
| db, err := NewViewDB() |
| if err != nil { |
| b.Fatal(err) |
| } |
| for _, id := range testSet { |
| if err := db.Save(&Container{ID: id}); err != nil { |
| b.Fatal(err) |
| } |
| l := rand.Intn(12) + 12 |
| testKeys = append(testKeys, id[:l]) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| for _, id := range testKeys { |
| if res, err := db.GetByPrefix(id); err != nil { |
| b.Fatal(res, err) |
| } |
| } |
| } |
| } |
| |
| func BenchmarkDBGetByPrefix500(b *testing.B) { |
| var testSet []string |
| var testKeys []string |
| for i := 0; i < 500; i++ { |
| testSet = append(testSet, stringid.GenerateRandomID()) |
| } |
| db, err := NewViewDB() |
| if err != nil { |
| b.Fatal(err) |
| } |
| for _, id := range testSet { |
| if err := db.Save(&Container{ID: id}); err != nil { |
| b.Fatal(err) |
| } |
| l := rand.Intn(12) + 12 |
| testKeys = append(testKeys, id[:l]) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| for _, id := range testKeys { |
| if res, err := db.GetByPrefix(id); err != nil { |
| b.Fatal(res, err) |
| } |
| } |
| } |
| } |