From 7a7a9a282c42b6cdfa26f5c515e238d2e782b446 Mon Sep 17 00:00:00 2001 From: Claudiu Belu Date: Mon, 30 Aug 2021 06:42:09 -0700 Subject: [PATCH] integration: Adds test for multilayer image import This test will make sure there aren't any issues with multilayered images during import. Keep in mind that in the case of multilayered images, they have to be unpacked first in order to be usable. Signed-off-by: Claudiu Belu --- integration/client/client_unix_test.go | 7 +-- integration/client/client_windows_test.go | 11 +++-- integration/client/import_test.go | 58 ++++++++++++++++++++++- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/integration/client/client_unix_test.go b/integration/client/client_unix_test.go index 278b4eaf6..7656064de 100644 --- a/integration/client/client_unix_test.go +++ b/integration/client/client_unix_test.go @@ -32,9 +32,10 @@ const ( ) var ( - testImage = "ghcr.io/containerd/busybox:1.32" - shortCommand = withProcessArgs("true") - longCommand = withProcessArgs("/bin/sh", "-c", "while true; do sleep 1; done") + testImage = "ghcr.io/containerd/busybox:1.32" + testMultiLayeredImage = "gcr.io/k8s-cri-containerd/volume-copy-up:2.1" + shortCommand = withProcessArgs("true") + longCommand = withProcessArgs("/bin/sh", "-c", "while true; do sleep 1; done") ) func TestImagePullSchema1WithEmptyLayers(t *testing.T) { diff --git a/integration/client/client_windows_test.go b/integration/client/client_windows_test.go index 575a06a64..532f177ba 100644 --- a/integration/client/client_windows_test.go +++ b/integration/client/client_windows_test.go @@ -30,11 +30,12 @@ const ( ) var ( - defaultRoot = filepath.Join(os.Getenv("programfiles"), "containerd", "root-test") - defaultState = filepath.Join(os.Getenv("programfiles"), "containerd", "state-test") - testImage string - shortCommand = withTrue() - longCommand = withProcessArgs("ping", "-t", "localhost") + defaultRoot = filepath.Join(os.Getenv("programfiles"), "containerd", "root-test") + defaultState = filepath.Join(os.Getenv("programfiles"), "containerd", "state-test") + testImage string + testMultiLayeredImage = "gcr.io/k8s-cri-containerd/volume-copy-up:2.1" + shortCommand = withTrue() + longCommand = withProcessArgs("ping", "-t", "localhost") ) func init() { diff --git a/integration/client/import_test.go b/integration/client/import_test.go index ed81d30b4..4b80ba0cd 100644 --- a/integration/client/import_test.go +++ b/integration/client/import_test.go @@ -27,12 +27,17 @@ import ( "reflect" "runtime" "testing" + "time" . "github.com/containerd/containerd" "github.com/containerd/containerd/archive/compression" "github.com/containerd/containerd/archive/tartest" "github.com/containerd/containerd/images" "github.com/containerd/containerd/images/archive" + "github.com/containerd/containerd/leases" + "github.com/containerd/containerd/oci" + "github.com/containerd/containerd/platforms" + digest "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -41,6 +46,18 @@ import ( // TestExportAndImport exports testImage as a tar stream, // and import the tar stream as a new image. func TestExportAndImport(t *testing.T) { + testExportImport(t, testImage) +} + +// TestExportAndImportMultiLayer exports testMultiLayeredImage as a tar stream, +// and import the tar stream as a new image. This should ensure that imported +// images remain sane, and that the Garbage Collector won't delete part of its +// content. +func TestExportAndImportMultiLayer(t *testing.T) { + testExportImport(t, testMultiLayeredImage) +} + +func testExportImport(t *testing.T, imageName string) { if testing.Short() { t.Skip() } @@ -53,17 +70,19 @@ func TestExportAndImport(t *testing.T) { } defer client.Close() - _, err = client.Fetch(ctx, testImage) + _, err = client.Fetch(ctx, imageName) if err != nil { t.Fatal(err) } wb := bytes.NewBuffer(nil) - err = client.Export(ctx, wb, archive.WithAllPlatforms(), archive.WithImage(client.ImageService(), testImage)) + err = client.Export(ctx, wb, archive.WithPlatform(platforms.Default()), archive.WithImage(client.ImageService(), imageName)) if err != nil { t.Fatal(err) } + client.ImageService().Delete(ctx, imageName) + opts := []ImportOpt{ WithImageRefTranslator(archive.AddRefPrefix("foo/bar")), } @@ -72,6 +91,41 @@ func TestExportAndImport(t *testing.T) { t.Fatalf("Import failed: %+v", err) } + // We need to unpack the image, especially if it's multilayered. + for _, img := range imgrecs { + image := NewImage(client, img) + + // TODO: Show unpack status + t.Logf("unpacking %s (%s)...", img.Name, img.Target.Digest) + err = image.Unpack(ctx, "") + if err != nil { + t.Fatalf("Error while unpacking image: %+v", err) + } + t.Log("done") + } + + // we're triggering the Garbage Collector to do its job. + ls := client.LeasesService() + l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(time.Hour)) + if err != nil { + t.Fatalf("Error while creating lease: %+v", err) + } + if err = ls.Delete(ctx, l, leases.SynchronousDelete); err != nil { + t.Fatalf("Error while deleting lease: %+v", err) + } + + image, err := client.GetImage(ctx, imageName) + if err != nil { + t.Fatal(err) + } + + id := t.Name() + container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image))) + if err != nil { + t.Fatalf("Error while creating container: %+v", err) + } + container.Delete(ctx, WithSnapshotCleanup) + for _, imgrec := range imgrecs { if imgrec.Name == testImage { continue