github.com/rish1988/moby@v25.0.2+incompatible/daemon/containerd/image_test.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"math/rand"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/containerd/containerd/images"
    11  	"github.com/containerd/containerd/metadata"
    12  	"github.com/containerd/containerd/namespaces"
    13  	"github.com/containerd/log/logtest"
    14  	"github.com/distribution/reference"
    15  	dockerimages "github.com/docker/docker/daemon/images"
    16  	"github.com/opencontainers/go-digest"
    17  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    18  
    19  	"go.etcd.io/bbolt"
    20  
    21  	"gotest.tools/v3/assert"
    22  	is "gotest.tools/v3/assert/cmp"
    23  )
    24  
    25  func TestLookup(t *testing.T) {
    26  	ctx := namespaces.WithNamespace(context.TODO(), "testing")
    27  	ctx = logtest.WithT(ctx, t)
    28  	mdb := newTestDB(ctx, t)
    29  	service := &ImageService{
    30  		images: metadata.NewImageStore(mdb),
    31  	}
    32  
    33  	ubuntuLatest := images.Image{
    34  		Name:   "docker.io/library/ubuntu:latest",
    35  		Target: desc(10),
    36  	}
    37  	ubuntuLatestWithDigest := images.Image{
    38  		Name:   "docker.io/library/ubuntu:latest@" + digestFor(10).String(),
    39  		Target: desc(10),
    40  	}
    41  	ubuntuLatestWithOldDigest := images.Image{
    42  		Name:   "docker.io/library/ubuntu:latest@" + digestFor(11).String(),
    43  		Target: desc(11),
    44  	}
    45  	ambiguousShortName := images.Image{
    46  		Name:   "docker.io/library/abcdef:latest",
    47  		Target: desc(12),
    48  	}
    49  	ambiguousShortNameWithDigest := images.Image{
    50  		Name:   "docker.io/library/abcdef:latest@" + digestFor(12).String(),
    51  		Target: desc(12),
    52  	}
    53  	shortNameIsHashAlgorithm := images.Image{
    54  		Name:   "docker.io/library/sha256:defcab",
    55  		Target: desc(13),
    56  	}
    57  
    58  	testImages := []images.Image{
    59  		ubuntuLatest,
    60  		ubuntuLatestWithDigest,
    61  		ubuntuLatestWithOldDigest,
    62  		ambiguousShortName,
    63  		ambiguousShortNameWithDigest,
    64  		shortNameIsHashAlgorithm,
    65  		{
    66  			Name:   "docker.io/test/volatile:retried",
    67  			Target: desc(14),
    68  		},
    69  		{
    70  			Name:   "docker.io/test/volatile:inconsistent",
    71  			Target: desc(15),
    72  		},
    73  	}
    74  	for _, img := range testImages {
    75  		if _, err := service.images.Create(ctx, img); err != nil {
    76  			t.Fatalf("failed to create image %q: %v", img.Name, err)
    77  		}
    78  	}
    79  
    80  	for _, tc := range []struct {
    81  		lookup string
    82  		img    *images.Image
    83  		all    []images.Image
    84  		err    error
    85  	}{
    86  		{
    87  			// Get ubuntu images with default "latest" tag
    88  			lookup: "ubuntu",
    89  			img:    &ubuntuLatest,
    90  			all:    []images.Image{ubuntuLatest, ubuntuLatestWithDigest},
    91  		},
    92  		{
    93  			// Get all images by image id
    94  			lookup: ubuntuLatest.Target.Digest.String(),
    95  			img:    nil,
    96  			all:    []images.Image{ubuntuLatest, ubuntuLatestWithDigest},
    97  		},
    98  		{
    99  			// Fail to lookup reference with no tag, reference has both tag and digest
   100  			lookup: "ubuntu@" + ubuntuLatestWithOldDigest.Target.Digest.String(),
   101  			img:    nil,
   102  			all:    []images.Image{ubuntuLatestWithOldDigest},
   103  		},
   104  		{
   105  			// Get all image with both tag and digest
   106  			lookup: "ubuntu:latest@" + ubuntuLatestWithOldDigest.Target.Digest.String(),
   107  			img:    &ubuntuLatestWithOldDigest,
   108  			all:    []images.Image{ubuntuLatestWithOldDigest},
   109  		},
   110  		{
   111  			// Fail to lookup reference with no tag for digest that doesn't exist
   112  			lookup: "ubuntu@" + digestFor(20).String(),
   113  			err:    dockerimages.ErrImageDoesNotExist{Ref: nameDigest("ubuntu", digestFor(20))},
   114  		},
   115  		{
   116  			// Fail to lookup reference with nonexistent tag
   117  			lookup: "ubuntu:nonexistent",
   118  			err:    dockerimages.ErrImageDoesNotExist{Ref: nameTag("ubuntu", "nonexistent")},
   119  		},
   120  		{
   121  			// Get abcdef image which also matches short image id
   122  			lookup: "abcdef",
   123  			img:    &ambiguousShortName,
   124  			all:    []images.Image{ambiguousShortName, ambiguousShortNameWithDigest},
   125  		},
   126  		{
   127  			// Fail to lookup image named "sha256" with tag that doesn't exist
   128  			lookup: "sha256:abcdef",
   129  			err:    dockerimages.ErrImageDoesNotExist{Ref: nameTag("sha256", "abcdef")},
   130  		},
   131  		{
   132  			// Lookup with shortened image id
   133  			lookup: ambiguousShortName.Target.Digest.Encoded()[:8],
   134  			img:    nil,
   135  			all:    []images.Image{ambiguousShortName, ambiguousShortNameWithDigest},
   136  		},
   137  		{
   138  			// Lookup an actual image named "sha256" in the default namespace
   139  			lookup: "sha256:defcab",
   140  			img:    &shortNameIsHashAlgorithm,
   141  			all:    []images.Image{shortNameIsHashAlgorithm},
   142  		},
   143  	} {
   144  		tc := tc
   145  		t.Run(tc.lookup, func(t *testing.T) {
   146  			t.Parallel()
   147  			img, all, err := service.resolveAllReferences(ctx, tc.lookup)
   148  			if tc.err == nil {
   149  				assert.NilError(t, err)
   150  			} else {
   151  				assert.Error(t, err, tc.err.Error())
   152  			}
   153  			if tc.img == nil {
   154  				assert.Assert(t, is.Nil(img))
   155  			} else {
   156  				assert.Assert(t, img != nil)
   157  				assert.Check(t, is.Equal(img.Name, tc.img.Name))
   158  				assert.Check(t, is.Equal(img.Target.Digest, tc.img.Target.Digest))
   159  			}
   160  
   161  			assert.Assert(t, is.Len(tc.all, len(all)))
   162  
   163  			// Order should match
   164  			for i := range all {
   165  				assert.Check(t, is.Equal(all[i].Name, tc.all[i].Name), "image[%d]", i)
   166  				assert.Check(t, is.Equal(all[i].Target.Digest, tc.all[i].Target.Digest), "image[%d]", i)
   167  			}
   168  		})
   169  	}
   170  
   171  	t.Run("fail-inconsistency", func(t *testing.T) {
   172  		service := &ImageService{
   173  			images: &mutateOnGetImageStore{
   174  				Store: service.images,
   175  				getMutations: []images.Image{
   176  					{
   177  						Name:   "docker.io/test/volatile:inconsistent",
   178  						Target: desc(18),
   179  					},
   180  					{
   181  						Name:   "docker.io/test/volatile:inconsistent",
   182  						Target: desc(19),
   183  					},
   184  					{
   185  						Name:   "docker.io/test/volatile:inconsistent",
   186  						Target: desc(20),
   187  					},
   188  					{
   189  						Name:   "docker.io/test/volatile:inconsistent",
   190  						Target: desc(21),
   191  					},
   192  					{
   193  						Name:   "docker.io/test/volatile:inconsistent",
   194  						Target: desc(22),
   195  					},
   196  				},
   197  				t: t,
   198  			},
   199  		}
   200  
   201  		_, _, err := service.resolveAllReferences(ctx, "test/volatile:inconsistent")
   202  		assert.ErrorIs(t, err, errInconsistentData)
   203  	})
   204  
   205  	t.Run("retry-inconsistency", func(t *testing.T) {
   206  		service := &ImageService{
   207  			images: &mutateOnGetImageStore{
   208  				Store: service.images,
   209  				getMutations: []images.Image{
   210  					{
   211  						Name:   "docker.io/test/volatile:retried",
   212  						Target: desc(16),
   213  					},
   214  					{
   215  						Name:   "docker.io/test/volatile:retried",
   216  						Target: desc(17),
   217  					},
   218  				},
   219  				t: t,
   220  			},
   221  		}
   222  
   223  		img, all, err := service.resolveAllReferences(ctx, "test/volatile:retried")
   224  		assert.NilError(t, err)
   225  
   226  		assert.Assert(t, img != nil)
   227  		assert.Check(t, is.Equal(img.Name, "docker.io/test/volatile:retried"))
   228  		assert.Check(t, is.Equal(img.Target.Digest, digestFor(17)))
   229  		assert.Assert(t, is.Len(all, 1))
   230  		assert.Check(t, is.Equal(all[0].Name, "docker.io/test/volatile:retried"))
   231  		assert.Check(t, is.Equal(all[0].Target.Digest, digestFor(17)))
   232  	})
   233  }
   234  
   235  type mutateOnGetImageStore struct {
   236  	images.Store
   237  	getMutations []images.Image
   238  	t            *testing.T
   239  }
   240  
   241  func (m *mutateOnGetImageStore) Get(ctx context.Context, name string) (images.Image, error) {
   242  	img, err := m.Store.Get(ctx, name)
   243  	if len(m.getMutations) > 0 {
   244  		m.Store.Update(ctx, m.getMutations[0])
   245  		m.getMutations = m.getMutations[1:]
   246  		m.t.Logf("Get %s", name)
   247  	}
   248  	return img, err
   249  }
   250  
   251  func nameDigest(name string, dgst digest.Digest) reference.Reference {
   252  	named, _ := reference.WithName(name)
   253  	digested, _ := reference.WithDigest(named, dgst)
   254  	return digested
   255  }
   256  
   257  func nameTag(name, tag string) reference.Reference {
   258  	named, _ := reference.WithName(name)
   259  	tagged, _ := reference.WithTag(named, tag)
   260  	return tagged
   261  }
   262  
   263  func desc(size int64) ocispec.Descriptor {
   264  	return ocispec.Descriptor{
   265  		Digest:    digestFor(size),
   266  		Size:      size,
   267  		MediaType: ocispec.MediaTypeImageIndex,
   268  	}
   269  
   270  }
   271  
   272  func digestFor(i int64) digest.Digest {
   273  	r := rand.New(rand.NewSource(i))
   274  	dgstr := digest.SHA256.Digester()
   275  	_, err := io.Copy(dgstr.Hash(), io.LimitReader(r, i))
   276  	if err != nil {
   277  		panic(err)
   278  	}
   279  	return dgstr.Digest()
   280  }
   281  
   282  func newTestDB(ctx context.Context, t *testing.T) *metadata.DB {
   283  	t.Helper()
   284  
   285  	p := filepath.Join(t.TempDir(), "metadata")
   286  	bdb, err := bbolt.Open(p, 0600, &bbolt.Options{})
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	t.Cleanup(func() { bdb.Close() })
   291  
   292  	mdb := metadata.NewDB(bdb, nil, nil)
   293  	if err := mdb.Init(ctx); err != nil {
   294  		t.Fatal(err)
   295  	}
   296  
   297  	return mdb
   298  }