mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
179 lines
6.0 KiB
Go
179 lines
6.0 KiB
Go
package containerd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/containerd/containerd/v2/core/content"
|
|
"github.com/containerd/containerd/v2/pkg/namespaces"
|
|
"github.com/containerd/containerd/v2/plugins/content/local"
|
|
cerrdefs "github.com/containerd/errdefs"
|
|
"github.com/containerd/platforms"
|
|
"github.com/moby/go-archive"
|
|
"github.com/moby/go-archive/compression"
|
|
"github.com/moby/moby/v2/daemon/server/imagebackend"
|
|
"github.com/moby/moby/v2/internal/testutil/labelstore"
|
|
"github.com/moby/moby/v2/internal/testutil/specialimage"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
)
|
|
|
|
func TestImageLoad(t *testing.T) {
|
|
linuxAmd64 := ocispec.Platform{OS: "linux", Architecture: "amd64"}
|
|
linuxArm64 := ocispec.Platform{OS: "linux", Architecture: "arm64"}
|
|
linuxArmv5 := ocispec.Platform{OS: "linux", Architecture: "arm", Variant: "v5"}
|
|
linuxRiscv64 := ocispec.Platform{OS: "linux", Architecture: "riskv64"}
|
|
|
|
ctx := namespaces.WithNamespace(t.Context(), "testing-"+t.Name())
|
|
|
|
store, err := local.NewLabeledStore(t.TempDir(), &labelstore.InMemory{})
|
|
assert.NilError(t, err)
|
|
|
|
imgSvc := fakeImageService(t, ctx, store)
|
|
// Mock the daemon platform.
|
|
imgSvc.defaultPlatformOverride = platforms.Only(linuxAmd64)
|
|
|
|
tryLoad := func(ctx context.Context, t *testing.T, dir string, platformList []ocispec.Platform) error {
|
|
tarRc, err := archive.Tar(dir, compression.None)
|
|
assert.NilError(t, err)
|
|
defer tarRc.Close()
|
|
|
|
buf := bytes.Buffer{}
|
|
|
|
defer func() {
|
|
t.Log(buf.String())
|
|
}()
|
|
|
|
return imgSvc.LoadImage(ctx, tarRc, platformList, &buf, true)
|
|
}
|
|
|
|
cleanup := func(ctx context.Context, t *testing.T) {
|
|
// Remove all existing images to start fresh
|
|
images, err := imgSvc.Images(ctx, imagebackend.ListOptions{})
|
|
assert.NilError(t, err)
|
|
for _, img := range images {
|
|
_, err := imgSvc.ImageDelete(ctx, img.ID, imagebackend.RemoveOptions{PruneChildren: true})
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
// Remove all content from the store
|
|
assert.NilError(t, store.Walk(ctx, func(info content.Info) error {
|
|
return store.Delete(ctx, info.Digest)
|
|
}), "failed to delete all content")
|
|
}
|
|
|
|
t.Run("empty index", func(t *testing.T) {
|
|
imgDataDir := t.TempDir()
|
|
_, err := specialimage.EmptyIndex(imgDataDir)
|
|
assert.NilError(t, err)
|
|
|
|
err = tryLoad(ctx, t, imgDataDir, []ocispec.Platform{linuxAmd64})
|
|
assert.Check(t, is.Error(err, "image emptyindex:latest was loaded, but doesn't provide the requested platform ([linux/amd64])"))
|
|
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
|
|
})
|
|
cleanup(ctx, t)
|
|
|
|
t.Run("single platform", func(t *testing.T) {
|
|
imgDataDir := t.TempDir()
|
|
r := rand.NewSource(0x9127371238)
|
|
_, err = specialimage.RandomSinglePlatform(imgDataDir, linuxAmd64, r)
|
|
assert.NilError(t, err)
|
|
|
|
platforms := []ocispec.Platform{linuxAmd64}
|
|
err = tryLoad(ctx, t, imgDataDir, platforms)
|
|
assert.NilError(t, err)
|
|
|
|
err = tryLoad(ctx, t, imgDataDir, []ocispec.Platform{linuxArm64})
|
|
assert.Check(t, is.ErrorContains(err, "doesn't provide the requested platform ([linux/arm64])"))
|
|
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
|
|
})
|
|
cleanup(ctx, t)
|
|
|
|
t.Run("multi-platform image", func(t *testing.T) {
|
|
imgDataDir := t.TempDir()
|
|
imgRef := "multiplatform:latest"
|
|
_, mfstDescs, err := specialimage.MultiPlatform(imgDataDir, imgRef, []ocispec.Platform{linuxAmd64, linuxArm64, linuxRiscv64})
|
|
assert.NilError(t, err)
|
|
|
|
t.Run("one platform in index", func(t *testing.T) {
|
|
platforms := []ocispec.Platform{linuxAmd64}
|
|
err = tryLoad(ctx, t, imgDataDir, platforms)
|
|
assert.NilError(t, err)
|
|
|
|
// verify that the loaded image has the correct platform
|
|
err = verifyImagePlatforms(ctx, imgSvc, imgRef, platforms)
|
|
assert.NilError(t, err)
|
|
})
|
|
cleanup(ctx, t)
|
|
|
|
t.Run("all platforms in index", func(t *testing.T) {
|
|
platforms := []ocispec.Platform{linuxAmd64, linuxArm64, linuxRiscv64}
|
|
err = tryLoad(ctx, t, imgDataDir, platforms)
|
|
assert.NilError(t, err)
|
|
|
|
// verify that the loaded image has the correct platforms
|
|
err = verifyImagePlatforms(ctx, imgSvc, imgRef, platforms)
|
|
assert.NilError(t, err)
|
|
})
|
|
cleanup(ctx, t)
|
|
|
|
t.Run("platform not included in index", func(t *testing.T) {
|
|
err = tryLoad(ctx, t, imgDataDir, []ocispec.Platform{linuxArmv5})
|
|
assert.Check(t, is.Error(err, "image multiplatform:latest was loaded, but doesn't provide the requested platform ([linux/arm/v5])"))
|
|
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
|
|
})
|
|
cleanup(ctx, t)
|
|
|
|
t.Run("platform included but blobs missing", func(t *testing.T) {
|
|
// Assumption: arm64 image is second in the index (implementation detail of specialimage.MultiPlatform)
|
|
mfstDesc := mfstDescs[1]
|
|
assert.Assert(t, mfstDesc.Platform.Architecture == linuxArm64.Architecture)
|
|
assert.Assert(t, mfstDesc.Platform.Variant == linuxArm64.Variant)
|
|
|
|
t.Log(mfstDesc.Digest)
|
|
|
|
// Delete arm64 manifest
|
|
mfstPath := filepath.Join(imgDataDir, "blobs/sha256", mfstDesc.Digest.Encoded())
|
|
assert.NilError(t, os.Remove(mfstPath))
|
|
|
|
err = tryLoad(ctx, t, imgDataDir, []ocispec.Platform{linuxArm64})
|
|
assert.Check(t, is.ErrorContains(err, "requested platform(s) ([linux/arm64]) found, but some content is missing"))
|
|
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
|
|
})
|
|
cleanup(ctx, t)
|
|
})
|
|
}
|
|
|
|
func verifyImagePlatforms(ctx context.Context, imgSvc *ImageService, imgRef string, expectedPlatforms []ocispec.Platform) error {
|
|
// get the manifest(s) for the image
|
|
img, err := imgSvc.ImageInspect(ctx, imgRef, imagebackend.ImageInspectOpts{Manifests: true})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// verify that the image manifest has the expected platforms
|
|
for _, ep := range expectedPlatforms {
|
|
want := platforms.FormatAll(ep)
|
|
found := false
|
|
for _, m := range img.Manifests {
|
|
if m.Descriptor.Platform != nil {
|
|
got := platforms.FormatAll(*m.Descriptor.Platform)
|
|
if got == want {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("expected platform %q not found in loaded images", want)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|