diff --git a/integration/client/client_unix_test.go b/integration/client/client_unix_test.go index 60b7150b8..38fe9c38a 100644 --- a/integration/client/client_unix_test.go +++ b/integration/client/client_unix_test.go @@ -33,9 +33,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 f8de07eb4..c8a225a0b 100644 --- a/integration/client/import_test.go +++ b/integration/client/import_test.go @@ -26,12 +26,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" @@ -40,6 +45,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() } @@ -52,17 +69,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")), } @@ -71,6 +90,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