Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions internal/lib/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
metadata "github.com/checkpoint-restore/checkpointctl/lib"
"github.com/checkpoint-restore/go-criu/v5/stats"
"github.com/containers/podman/v4/pkg/checkpoint/crutils"
"github.com/containers/storage/pkg/archive"
"github.com/cri-o/cri-o/internal/oci"
"github.com/opencontainers/runtime-tools/generate"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -63,8 +64,43 @@ func (c *ContainerServer) ContainerRestore(ctx context.Context, opts *ContainerC
}

if ctr.RestoreArchive() != "" {
if err := crutils.CRImportCheckpointWithoutConfig(ctr.Dir(), ctr.RestoreArchive()); err != nil {
return "", err
if ctr.RestoreIsOCIImage() {
logrus.Debugf("Restoring from %v", ctr.RestoreArchive())
imageMountPoint, err := c.StorageImageServer().GetStore().MountImage(ctr.RestoreArchive(), nil, "")
if err != nil {
return "", err
}
logrus.Debugf("Checkpoint image mounted at %v", imageMountPoint)
defer func() {
_, err := c.StorageImageServer().GetStore().UnmountImage(ctr.RestoreArchive(), true)
if err != nil {
logrus.Errorf("Failed to unmount checkpoint image: %q", err)
}
}()

// Import all checkpoint files except ConfigDumpFile and SpecDumpFile. We
// generate new container config files to enable to specifying a new
// container name.
checkpoint := []string{
"artifacts",
metadata.CheckpointDirectory,
metadata.DevShmCheckpointTar,
metadata.RootFsDiffTar,
metadata.DeletedFilesFile,
metadata.PodOptionsFile,
metadata.PodDumpFile,
}
for _, name := range checkpoint {
src := filepath.Join(imageMountPoint, name)
dst := filepath.Join(ctr.Dir(), name)
if err := archive.NewDefaultArchiver().CopyWithTar(src, dst); err != nil {
logrus.Debugf("Can't import '%s' from checkpoint image", name)
}
}
} else {
if err := crutils.CRImportCheckpointWithoutConfig(ctr.Dir(), ctr.RestoreArchive()); err != nil {
return "", err
}
}
if err := c.restoreFileSystemChanges(ctr, mountPoint); err != nil {
return "", err
Expand Down
78 changes: 72 additions & 6 deletions internal/lib/restore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package lib_test

import (
"context"
"fmt"
"io"
"os"
"path/filepath"
Expand Down Expand Up @@ -131,14 +130,14 @@ var _ = t.Describe("ContainerRestore", func() {
Expect(err.Error()).To(ContainSubstring(`failed to restore container containerID`))
})
})
t.Describe("ContainerRestore", func() {
t.Describe("ContainerRestore from archive", func() {
It("should fail with failed to restore", func() {
// Given
var opts lib.ContainerCheckpointRestoreOptions

opts.Container = containerID

createDummyConfig()
Expect(os.WriteFile("config.json", []byte(`{"linux":{},"process":{},"mounts":[{"type":"not-bind"},{"type":"bind","source":"/"}]}`), 0o644)).To(BeNil())
addContainerAndSandbox()

myContainer.SetStateAndSpoofPid(&oci.ContainerState{
Expand Down Expand Up @@ -174,7 +173,7 @@ var _ = t.Describe("ContainerRestore", func() {
defer os.RemoveAll("rootfs-diff.tar")
rootfs.Close()

err = os.WriteFile("deleted.files", []byte(`{}`), 0o644)
err = os.WriteFile("deleted.files", []byte(`[]`), 0o644)
Expect(err).To(BeNil())
defer os.RemoveAll("deleted.files")

Expand All @@ -191,7 +190,75 @@ var _ = t.Describe("ContainerRestore", func() {
_, err = io.Copy(outFile, input)
Expect(err).To(BeNil())

opts.TargetFile = "archive.tar"
myContainer.SetRestoreArchive("archive.tar")
err = os.Mkdir("bundle", 0o700)
Expect(err).To(BeNil())
setupInfraContainerWithPid(42, "bundle")
defer os.RemoveAll("bundle")

// When
res, err := sut.ContainerRestore(context.Background(), &opts)

// Then
Expect(err).NotTo(BeNil())
Expect(res).To(Equal(""))
Expect(err.Error()).To(ContainSubstring(`failed to restore container containerID: failed to`))
})
})
t.Describe("ContainerRestore from OCI images", func() {
It("should fail with failed to restore", func() {
// Given
var opts lib.ContainerCheckpointRestoreOptions

opts.Container = containerID

createDummyConfig()
addContainerAndSandbox()

myContainer.SetStateAndSpoofPid(&oci.ContainerState{
State: specs.State{Status: oci.ContainerStateStopped},
})

myContainer.SetSpec(&specs.Spec{
Version: "1.0.0",
Process: &specs.Process{},
Linux: &specs.Linux{},
})

myContainer.SetRestoreIsOCIImage(true)

gomock.InOrder(
storeMock.EXPECT().Mount(gomock.Any(), gomock.Any()).Return("/tmp/", nil),
storeMock.EXPECT().MountImage(gomock.Any(), gomock.Any(), gomock.Any()).
Return("", nil),
storeMock.EXPECT().UnmountImage(gomock.Any(), true).
Return(false, nil),
)

err := os.WriteFile("spec.dump", []byte(`{"annotations":{"io.kubernetes.cri-o.Metadata":"{\"name\":\"container-to-restore\"}"}}`), 0o644)
Expect(err).To(BeNil())
defer os.RemoveAll("spec.dump")
err = os.WriteFile("config.dump", []byte(`{"rootfsImageName": "image"}`), 0o644)
Expect(err).To(BeNil())
defer os.RemoveAll("config.dump")

err = os.Mkdir("checkpoint", 0o700)
Expect(err).To(BeNil())
defer os.RemoveAll("checkpoint")
inventory, err := os.OpenFile("checkpoint/inventory.img", os.O_RDONLY|os.O_CREATE, 0o644)
Expect(err).To(BeNil())
inventory.Close()

rootfs, err := os.OpenFile("rootfs-diff.tar", os.O_RDONLY|os.O_CREATE, 0o644)
Expect(err).To(BeNil())
defer os.RemoveAll("rootfs-diff.tar")
rootfs.Close()

err = os.WriteFile("deleted.files", []byte(`[]`), 0o644)
Expect(err).To(BeNil())
defer os.RemoveAll("deleted.files")

myContainer.SetRestoreArchive("localhost/checkpoint-image:tag1")
err = os.Mkdir("bundle", 0o700)
Expect(err).To(BeNil())
setupInfraContainerWithPid(42, "bundle")
Expand All @@ -203,7 +270,6 @@ var _ = t.Describe("ContainerRestore", func() {
// Then
Expect(err).NotTo(BeNil())
Expect(res).To(Equal(""))
fmt.Printf("%#v\n", config.Runtimes)
Expect(err.Error()).To(ContainSubstring(`failed to restore container containerID: failed to`))
})
})
Expand Down
9 changes: 9 additions & 0 deletions internal/oci/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type Container struct {
pidns nsmgr.Namespace
restore bool
restoreArchive string
restoreIsOCIImage bool
}

func (c *Container) CRIAttributes() *types.ContainerAttributes {
Expand Down Expand Up @@ -681,3 +682,11 @@ func (c *Container) RestoreArchive() string {
func (c *Container) SetRestoreArchive(restoreArchive string) {
c.restoreArchive = restoreArchive
}

func (c *Container) RestoreIsOCIImage() bool {
return c.restoreIsOCIImage
}

func (c *Container) SetRestoreIsOCIImage(restoreIsOCIImage bool) {
c.restoreIsOCIImage = restoreIsOCIImage
}
36 changes: 36 additions & 0 deletions internal/oci/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ var _ = t.Describe("Container", func() {
Expect(sut.CreatedAt().UnixNano()).
To(BeNumerically("<", time.Now().UnixNano()))
Expect(sut.Spoofed()).To(Equal(false))
Expect(sut.Restore()).To(Equal(false))
Expect(sut.RestoreArchive()).To(Equal(""))
Expect(sut.RestoreIsOCIImage()).To(Equal(false))
})

It("should succeed to set the spec", func() {
Expand Down Expand Up @@ -135,6 +138,39 @@ var _ = t.Describe("Container", func() {
Expect(sut.State().Error).To(Equal(err.Error()))
})

It("should succeed to set restore", func() {
// Given
restore := true

// When
sut.SetRestore(restore)

// Then
Expect(sut.Restore()).To(Equal(restore))
})

It("should succeed to set restore is oci image", func() {
// Given
restore := true

// When
sut.SetRestoreIsOCIImage(restore)

// Then
Expect(sut.RestoreIsOCIImage()).To(Equal(restore))
})

It("should succeed to set restore archive", func() {
// Given
restoreArchive := "image-name"

// When
sut.SetRestoreArchive(restoreArchive)

// Then
Expect(sut.RestoreArchive()).To(Equal(restoreArchive))
})

It("should succeed to set start failed with nil error", func() {
// Given
// When
Expand Down
2 changes: 1 addition & 1 deletion internal/oci/oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ var _ = t.Describe("Oci", func() {

// Then
Expect(err).NotTo(BeNil())
Expect(err.Error()).To(ContainSubstring("failed to wait"))
Expect(err.Error()).To(ContainSubstring("failed"))
})
It("RestoreContainer should fail with missing inventory", func() {
if !criu.CheckForCriu(criu.PodCriuVersion) {
Expand Down
21 changes: 21 additions & 0 deletions internal/storage/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type ImageResult struct {
PreviousName string
Labels map[string]string
OCIConfig *specs.Image
Annotations map[string]string
}

type indexInfo struct {
Expand All @@ -78,6 +79,7 @@ type imageCacheItem struct {
size *uint64
configDigest digest.Digest
info *types.ImageInspectInfo
annotations map[string]string
}

type imageCache map[string]imageCacheItem
Expand Down Expand Up @@ -231,11 +233,29 @@ func (svc *imageService) buildImageCacheItem(systemContext *types.SystemContext,
return imageCacheItem{}, fmt.Errorf("inspecting image: %w", err)
}

rawSource, err := ref.NewImageSource(svc.ctx, systemContext)
if err != nil {
return imageCacheItem{}, err
}
defer rawSource.Close()

topManifestBlob, manifestType, err := rawSource.GetManifest(svc.ctx, nil)
if err != nil {
return imageCacheItem{}, err
}
var ociManifest specs.Manifest
if manifestType == specs.MediaTypeImageManifest {
if err := json.Unmarshal(topManifestBlob, &ociManifest); err != nil {
return imageCacheItem{}, err
}
}

return imageCacheItem{
config: imageConfig,
size: size,
configDigest: configDigest,
info: info,
annotations: ociManifest.Annotations,
}, nil
}

Expand Down Expand Up @@ -265,6 +285,7 @@ func (svc *imageService) buildImageResult(image *storage.Image, cacheItem imageC
PreviousName: previousName,
Labels: cacheItem.info.Labels,
OCIConfig: cacheItem.config,
Annotations: cacheItem.annotations,
}
}

Expand Down
32 changes: 32 additions & 0 deletions internal/storage/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,18 @@ var _ = t.Describe("Image", func() {
}, nil),
// buildImageCacheItem
mockNewImage(storeMock, testNormalizedImageName, testSHA256),
storeMock.EXPECT().Image(testNormalizedImageName).
Return(&cs.Image{
ID: testSHA256,
Names: []string{
testNormalizedImageName,
"localhost/a@sha256:" + testSHA256,
"localhost/b@sha256:" + testSHA256,
"localhost/c:latest",
},
}, nil),
storeMock.EXPECT().ImageBigData(testSHA256, gomock.Any()).
Return(nil, nil),
// makeRepoDigests
storeMock.EXPECT().ImageBigDataDigest(testSHA256, gomock.Any()).
Return(digest.Digest("a:"+testSHA256), nil),
Expand Down Expand Up @@ -493,6 +505,15 @@ var _ = t.Describe("Image", func() {
return inOrder(
// buildImageCacheItem:
mockNewImage(storeMock, testSHA256, testSHA256),
storeMock.EXPECT().Image(gomock.Any()).
Return(&cs.Image{
ID: testSHA256,
Names: []string{
"localhost/c:latest",
},
}, nil),
storeMock.EXPECT().ImageBigData(testSHA256, gomock.Any()).
Return(nil, nil),
// makeRepoDigests:
storeMock.EXPECT().ImageBigDataDigest(testSHA256, gomock.Any()).
Return(digest.Digest(""), nil),
Expand Down Expand Up @@ -526,6 +547,17 @@ var _ = t.Describe("Image", func() {
mockGetStoreImage(storeMock, testNormalizedImageName, testSHA256),
// buildImageCacheItem:
mockNewImage(storeMock, testNormalizedImageName, testSHA256),

storeMock.EXPECT().Image(gomock.Any()).
Return(&cs.Image{
ID: testSHA256,
Names: []string{
testNormalizedImageName,
},
}, nil),
storeMock.EXPECT().ImageBigData(testSHA256, gomock.Any()).
Return(nil, nil),

// makeRepoDigests:
storeMock.EXPECT().ImageBigDataDigest(testSHA256, gomock.Any()).
Return(digest.Digest(""), nil),
Expand Down
32 changes: 32 additions & 0 deletions pkg/annotations/checkpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package annotations

const (
// CheckpointAnnotationName is used by Container Checkpoint when creating a checkpoint image to specify the
// original human-readable name for the container.
CheckpointAnnotationName = "io.kubernetes.cri-o.annotations.checkpoint.name"

// CheckpointAnnotationRawImageName is used by Container Checkpoint when
// creating a checkpoint image to specify the original unprocessed name of
// the image used to create the container (as specified by the user).
CheckpointAnnotationRawImageName = "io.kubernetes.cri-o.annotations.checkpoint.rawImageName"

// CheckpointAnnotationRootfsImageID is used by Container Checkpoint when
// creating a checkpoint image to specify the original ID of the image used
// to create the container.
CheckpointAnnotationRootfsImageID = "io.kubernetes.cri-o.annotations.checkpoint.rootfsImageID"

// CheckpointAnnotationRootfsImageName is used by Container Checkpoint when
// creating a checkpoint image to specify the original image name used to
// create the container.
CheckpointAnnotationRootfsImageName = "io.kubernetes.cri-o.annotations.checkpoint.rootfsImageName"

// CheckpointAnnotationCRIOVersion is used by Container Checkpoint when
// creating a checkpoint image to specify the version of CRI-O used on the
// host where the checkpoint was created.
CheckpointAnnotationCRIOVersion = "io.kubernetes.cri-o.annotations.checkpoint.cri-o.version"

// CheckpointAnnotationCriuVersion is used by Container Checkpoint when
// creating a checkpoint image to specify the version of CRIU used on the
// host where the checkpoint was created.
CheckpointAnnotationCriuVersion = "io.kubernetes.cri-o.annotations.checkpoint.criu.version"
)
Loading