image_test.go 8.1 KB

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