Merge pull request #9146 from sitano:ivan_nocompressio

PiperOrigin-RevId: 555750100
This commit is contained in:
gVisor bot
2023-08-10 19:19:42 -07:00
13 changed files with 612 additions and 93 deletions
+8 -2
View File
@@ -7,7 +7,10 @@ package(
go_library(
name = "compressio",
srcs = ["compressio.go"],
srcs = [
"compressio.go",
"nocompressio.go",
],
visibility = ["//:sandbox"],
deps = ["//pkg/sync"],
)
@@ -15,6 +18,9 @@ go_library(
go_test(
name = "compressio_test",
size = "medium",
srcs = ["compressio_test.go"],
srcs = [
"compressio_test.go",
"nocompressio_test.go",
],
library = ":compressio",
)
+2 -1
View File
@@ -13,7 +13,8 @@
// limitations under the License.
// Package compressio provides parallel compression and decompression, as well
// as optional SHA-256 hashing.
// as optional SHA-256 hashing. It also provides another storage variant
// (nocompressio) that does not compress data but tracks its integrity.
//
// The stream format is defined as follows.
//
+5
View File
@@ -96,6 +96,11 @@ func doTest(t harness, opts testOpts) {
compressionTime := time.Since(compressionStartTime)
compressionRatio := float32(compressed.Len()) / float32(len(opts.Data))
if compressed.Len() == 0 {
// Data can't be corrupted if there is no data.
opts.CorruptData = false
}
// Decompress.
var decompressed bytes.Buffer
decompressionStartTime := time.Now()
+283
View File
@@ -0,0 +1,283 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compressio
import (
"bufio"
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"hash"
"io"
)
// nocompressio provides data storage that does not use data compression but
// offers optional data integrity via SHA-256 hashing.
//
// The stream format is defined as follows.
//
// /------------------------------------------------------\
// | data size (4-bytes) |
// +------------------------------------------------------+
// | data |
// +------------------------------------------------------+
// | (optional) hash (32-bytes) |
// +------------------------------------------------------+
// | data size (4-bytes) |
// +------------------------------------------------------+
// | ...... |
// \------------------------------------------------------/
//
// where each hash is calculated from the following items in order
//
// data
// data size
// SimpleReader is a reader from uncompressed image.
type SimpleReader struct {
// in is the source.
in io.Reader
// key is the key used to create hash objects.
key []byte
// h is the hash object.
h hash.Hash
// current data chunk size
chunkSize uint32
// current chunk position
done uint32
}
var _ io.Reader = (*SimpleReader)(nil)
const (
defaultBufSize = 256 * 1024
)
// NewSimpleReader returns a new (uncompressed) reader. If key is non-nil, the data stream
// is assumed to contain expected hash values. See package comments for
// details.
func NewSimpleReader(in io.Reader, key []byte) (*SimpleReader, error) {
r := &SimpleReader{
in: bufio.NewReaderSize(in, defaultBufSize),
key: key,
}
if key != nil {
r.h = hmac.New(sha256.New, key)
}
return r, nil
}
// ReadByte implements wire.Reader.ReadByte.
func (r *SimpleReader) ReadByte() (byte, error) {
var p [1]byte
n, err := r.Read(p[:])
if n != 1 {
return p[0], err
}
// Suppress EOF.
return p[0], nil
}
// Read implements io.Reader.Read.
func (r *SimpleReader) Read(p []byte) (int, error) {
var scratch [4]byte
if len(p) == 0 {
return r.in.Read(p)
}
// need next chunk?
if r.done >= r.chunkSize {
if _, err := io.ReadFull(r.in, scratch[:]); err != nil {
return 0, err
}
r.chunkSize = binary.BigEndian.Uint32(scratch[:])
r.done = 0
if r.key != nil {
r.h.Reset()
}
if r.chunkSize == 0 {
// this must not happen
return 0, io.ErrNoProgress
}
}
toRead := uint32(len(p))
// can't read more than whats left
if toRead > r.chunkSize-r.done {
toRead = r.chunkSize - r.done
}
n, err := r.in.Read(p[:toRead])
if err != nil {
if err == io.EOF {
// this only can happen if storage or data size is corrupted,
// but we have no other means to detect it earlier as we store
// hash after the data block.
return n, ErrHashMismatch
}
return n, err
}
if r.key != nil {
_, _ = r.h.Write(p[:n])
}
r.done += uint32(n)
if r.done >= r.chunkSize {
if r.key != nil {
binary.BigEndian.PutUint32(scratch[:], r.chunkSize)
r.h.Write(scratch[:4])
sum := r.h.Sum(nil)
readerSum := make([]byte, len(sum))
if _, err := io.ReadFull(r.in, readerSum); err != nil {
if err == io.EOF {
return n, io.ErrUnexpectedEOF
}
return n, err
}
if !hmac.Equal(readerSum, sum) {
return n, ErrHashMismatch
}
}
r.done = 0
r.chunkSize = 0
}
return n, nil
}
// SimpleWriter is a writer that does not compress.
type SimpleWriter struct {
// base is the underlying writer.
base io.Writer
// out is a buffered writer.
out *bufio.Writer
// key is the key used to create hash objects.
key []byte
// closed indicates whether the file has been closed.
closed bool
}
var _ io.Writer = (*SimpleWriter)(nil)
var _ io.Closer = (*SimpleWriter)(nil)
// NewSimpleWriter returns a new non-compressing writer. If key is non-nil, hash values are
// generated and written out for compressed bytes. See package comments for
// details.
func NewSimpleWriter(out io.Writer, key []byte) (*SimpleWriter, error) {
return &SimpleWriter{
base: out,
out: bufio.NewWriterSize(out, defaultBufSize),
key: key,
}, nil
}
// WriteByte implements wire.Writer.WriteByte.
//
// Note that this implementation is necessary on the object itself, as an
// interface-based dispatch cannot tell whether the array backing the slice
// escapes, therefore the all bytes written will generate an escape.
func (w *SimpleWriter) WriteByte(b byte) error {
var p [1]byte
p[0] = b
n, err := w.Write(p[:])
if n != 1 {
return err
}
return nil
}
// Write implements io.Writer.Write.
func (w *SimpleWriter) Write(p []byte) (int, error) {
var scratch [4]byte
// Did we close already?
if w.closed {
return 0, io.ErrUnexpectedEOF
}
l := uint32(len(p))
// chunk length
binary.BigEndian.PutUint32(scratch[:], l)
if _, err := w.out.Write(scratch[:4]); err != nil {
return 0, err
}
// Write out to the stream.
n, err := w.out.Write(p)
if err != nil {
return n, err
}
if w.key != nil {
h := hmac.New(sha256.New, w.key)
// chunk data
_, _ = h.Write(p)
// chunk length
binary.BigEndian.PutUint32(scratch[:], l)
h.Write(scratch[:4])
sum := h.Sum(nil)
if _, err := io.CopyN(w.out, bytes.NewReader(sum), int64(len(sum))); err != nil {
return n, err
}
}
return n, nil
}
// Close implements io.Closer.Close.
func (w *SimpleWriter) Close() error {
// Did we already close? After the call to Close, we always mark as
// closed, regardless of whether the flush is successful.
if w.closed {
return io.ErrUnexpectedEOF
}
w.closed = true
// Flush buffered writer
if err := w.out.Flush(); err != nil {
return err
}
// Close the underlying writer (if necessary).
if closer, ok := w.base.(io.Closer); ok {
return closer.Close()
}
w.out = nil
w.base = nil
return nil
}
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compressio
import (
"bytes"
"fmt"
"io"
"math/rand"
"testing"
"time"
)
func TestNoCompress(t *testing.T) {
rand.Seed(time.Now().Unix())
var (
data = initTest(t, 10*1024*1024)
data0 = data[:0]
data1 = data[:1]
data2 = data[:11]
data3 = data[:16]
data4 = data[:]
)
for _, data := range [][]byte{data0, data1, data2, data3, data4} {
for _, blockSize := range []uint32{1, 4, 1024, 4 * 1024, 16 * 1024} {
// Skip annoying tests; they just take too long.
if blockSize <= 16 && len(data) > 16 {
continue
}
for _, key := range [][]byte{nil, hashKey} {
for _, corruptData := range []bool{false, true} {
if key == nil && corruptData {
// No need to test corrupt data
// case when not doing hashing.
continue
}
// Do the compress test.
doTest(t, testOpts{
Name: fmt.Sprintf("len(data)=%d, blockSize=%d, key=%s, corruptData=%v", len(data), blockSize, string(key), corruptData),
Data: data,
NewWriter: func(b *bytes.Buffer) (io.Writer, error) {
return NewSimpleWriter(b, key)
},
NewReader: func(b *bytes.Buffer) (io.Reader, error) {
return NewSimpleReader(b, key)
},
CorruptData: corruptData,
})
}
}
}
}
}
+98 -7
View File
@@ -84,6 +84,68 @@ var ErrInvalidMetadataLength = fmt.Errorf("metadata length invalid, maximum size
// ErrMetadataInvalid is returned if passed metadata is invalid.
var ErrMetadataInvalid = fmt.Errorf("metadata invalid, can't start with _")
// ErrInvalidFlags is returned if passed flags set is invalid.
var ErrInvalidFlags = fmt.Errorf("flags set is invalid")
const (
compressionKey = "compression"
)
// CompressionLevel is the image compression level.
type CompressionLevel string
const (
// CompressionLevelFlateBestSpeed represents flate algorithm in best-speed mode.
CompressionLevelFlateBestSpeed = CompressionLevel("flate-best-speed")
// CompressionLevelNone represents the absence of any compression on an image.
CompressionLevelNone = CompressionLevel("none")
)
// Options is statefile options.
type Options struct {
// Compression is an image compression type/level.
Compression CompressionLevel
}
// WriteToMetadata save options to the metadata storage. Method returns the
// reference to the original metadata map to allow to be used in the chain calls.
func (o Options) WriteToMetadata(metadata map[string]string) map[string]string {
metadata[compressionKey] = string(o.Compression)
return metadata
}
// CompressionLevelFromString parses a string into the CompressionLevel.
func CompressionLevelFromString(val string) (CompressionLevel, error) {
switch val {
case string(CompressionLevelFlateBestSpeed):
return CompressionLevelFlateBestSpeed, nil
case string(CompressionLevelNone):
return CompressionLevelNone, nil
default:
return CompressionLevelNone, ErrInvalidFlags
}
}
// CompressionLevelFromMetadata returns image compression type stored in the metadata.
// If the metadata doesn't contain compression information the default behavior
// is the "flate-best-speed" state because the default behavior used to be to always
// compress. If the parameter is missing it will be set to default.
func CompressionLevelFromMetadata(metadata map[string]string) (CompressionLevel, error) {
var err error
compression := CompressionLevelFlateBestSpeed
if val, ok := metadata[compressionKey]; ok {
if compression, err = CompressionLevelFromString(val); err != nil {
return CompressionLevelNone, err
}
} else {
metadata[compressionKey] = string(compression)
}
return compression, nil
}
// WriteCloser is an io.Closer and wire.Writer.
type WriteCloser interface {
wire.Writer
@@ -123,6 +185,12 @@ func NewWriter(w io.Writer, key []byte, metadata map[string]string) (WriteCloser
metadata["_timestamp"] = time.Now().UTC().String()
defer delete(metadata, "_timestamp")
// Save compression state
compression, err := CompressionLevelFromMetadata(metadata)
if err != nil {
return nil, err
}
// Write the metadata.
b, err := json.Marshal(metadata)
if err != nil {
@@ -152,11 +220,15 @@ func NewWriter(w io.Writer, key []byte, metadata map[string]string) (WriteCloser
}
}
// Wrap in compression. We always use "best speed" mode here. When using
// "best compression" mode, there is usually only a little gain in file
// size reduction, which translate to even smaller gain in restore
// latency reduction, while inccuring much more CPU usage at save time.
return compressio.NewWriter(w, key, compressionChunkSize, flate.BestSpeed)
// Wrap in compression. When using "best compression" mode, there is usually
// only a little gain in file size reduction, which translate to even smaller
// gain in restore latency reduction, while inccuring much more CPU usage at
// save time.
if compression == CompressionLevelFlateBestSpeed {
return compressio.NewWriter(w, key, compressionChunkSize, flate.BestSpeed)
}
return compressio.NewSimpleWriter(w, key)
}
// MetadataUnsafe reads out the metadata from a state file without verifying any
@@ -245,10 +317,29 @@ func NewReader(r io.Reader, key []byte) (wire.Reader, map[string]string, error)
return nil, nil, err
}
// Wrap in compression.
cr, err := compressio.NewReader(r, key)
// Determine image compression state. If the metadata doesn't contain
// compression information the default behavior is the "compressed" state
// because the default behavior used to be to always compress.
compression, err := CompressionLevelFromMetadata(metadata)
if err != nil {
return nil, nil, err
}
// Pick correct reader
var cr wire.Reader
if compression == CompressionLevelFlateBestSpeed {
cr, err = compressio.NewReader(r, key)
} else if compression == CompressionLevelNone {
cr, err = compressio.NewSimpleReader(r, key)
} else {
// Should never occur, as it has the default path.
return nil, nil, fmt.Errorf("metadata contains invalid compression flag value: %v", compression)
}
if err != nil {
return nil, nil, err
}
return cr, metadata, nil
}
+93 -76
View File
@@ -46,6 +46,11 @@ type testCase struct {
func TestStatefile(t *testing.T) {
rand.Seed(time.Now().Unix())
compression := map[string]CompressionLevel{
"none": CompressionLevelNone,
"compressed": CompressionLevelFlateBestSpeed,
}
cases := []testCase{
// Various data sizes.
{"nil", nil, nil},
@@ -72,90 +77,102 @@ func TestStatefile(t *testing.T) {
{"two metadata", []byte("data"), map[string]string{"foo": "bar", "one": "two"}},
}
for _, c := range cases {
// Generate a key.
integrityKey, err := randomKey()
if err != nil {
t.Errorf("can't generate key: got %v, excepted nil", err)
continue
}
for cKey, compress := range compression {
t.Run(cKey, func(t *testing.T) {
for _, c := range cases {
// Generate a key.
integrityKey, err := randomKey()
if err != nil {
t.Errorf("can't generate key: got %v, excepted nil", err)
continue
}
t.Run(c.name, func(t *testing.T) {
for _, key := range [][]byte{nil, integrityKey} {
t.Run("key="+string(key), func(t *testing.T) {
// Encoding happens via a buffer.
var bufEncoded bytes.Buffer
var bufDecoded bytes.Buffer
// Save compression state
if c.metadata == nil {
c.metadata = map[string]string{}
}
// Do all the writing.
w, err := NewWriter(&bufEncoded, key, c.metadata)
if err != nil {
t.Fatalf("error creating writer: got %v, expected nil", err)
}
if _, err := io.Copy(w, bytes.NewBuffer(c.data)); err != nil {
t.Fatalf("error during write: got %v, expected nil", err)
}
c.metadata[compressionKey] = string(compress)
// Finish the sum.
if err := w.Close(); err != nil {
t.Fatalf("error during close: got %v, expected nil", err)
}
t.Run(c.name, func(t *testing.T) {
for _, key := range [][]byte{nil, integrityKey} {
t.Run("key="+string(key), func(t *testing.T) {
// Encoding happens via a buffer.
var bufEncoded bytes.Buffer
var bufDecoded bytes.Buffer
t.Logf("original data: %d bytes, encoded: %d bytes.",
len(c.data), len(bufEncoded.Bytes()))
// Do all the writing.
w, err := NewWriter(&bufEncoded, key, c.metadata)
if err != nil {
t.Fatalf("error creating writer: got %v, expected nil", err)
}
if _, err := io.Copy(w, bytes.NewBuffer(c.data)); err != nil {
t.Fatalf("error during write: got %v, expected nil", err)
}
// Do all the reading.
r, metadata, err := NewReader(bytes.NewReader(bufEncoded.Bytes()), key)
if err != nil {
t.Fatalf("error creating reader: got %v, expected nil", err)
}
if _, err := io.Copy(&bufDecoded, r); err != nil {
t.Fatalf("error during read: got %v, expected nil", err)
}
// Finish the sum.
if err := w.Close(); err != nil {
t.Fatalf("error during close: got %v, expected nil", err)
}
// Check that the data matches.
if !bytes.Equal(c.data, bufDecoded.Bytes()) {
t.Fatalf("data didn't match (%d vs %d bytes)", len(bufDecoded.Bytes()), len(c.data))
}
t.Logf("original data: %d bytes, encoded: %d bytes.",
len(c.data), len(bufEncoded.Bytes()))
// Check that the metadata matches.
for k, v := range c.metadata {
nv, ok := metadata[k]
if !ok {
t.Fatalf("missing metadata: %s", k)
}
if v != nv {
t.Fatalf("mismatched metdata for %s: got %s, expected %s", k, nv, v)
}
}
// Do all the reading.
r, metadata, err := NewReader(bytes.NewReader(bufEncoded.Bytes()), key)
if err != nil {
t.Fatalf("error creating reader: got %v, expected nil", err)
}
if _, err := io.Copy(&bufDecoded, r); err != nil {
t.Fatalf("error during read: got %v, expected nil", err)
}
// Change the data and verify that it fails.
if key != nil {
b := append([]byte(nil), bufEncoded.Bytes()...)
b[rand.Intn(len(b))]++
bufDecoded.Reset()
r, _, err = NewReader(bytes.NewReader(b), key)
if err == nil {
_, err = io.Copy(&bufDecoded, r)
}
if err == nil {
t.Error("got no error: expected error on data corruption")
}
}
// Check that the data matches.
if !bytes.Equal(c.data, bufDecoded.Bytes()) {
t.Fatalf("data didn't match (%d vs %d bytes)", len(bufDecoded.Bytes()), len(c.data))
}
// Change the key and verify that it fails.
newKey := integrityKey
if len(key) > 0 {
newKey = append([]byte{}, key...)
newKey[rand.Intn(len(newKey))]++
}
bufDecoded.Reset()
r, _, err = NewReader(bytes.NewReader(bufEncoded.Bytes()), newKey)
if err == nil {
_, err = io.Copy(&bufDecoded, r)
}
if err != compressio.ErrHashMismatch {
t.Errorf("got error: %v, expected ErrHashMismatch on key mismatch", err)
// Check that the metadata matches.
for k, v := range c.metadata {
nv, ok := metadata[k]
if !ok {
t.Fatalf("missing metadata: %s", k)
}
if v != nv {
t.Fatalf("mismatched metdata for %s: got %s, expected %s", k, nv, v)
}
}
// Change the data and verify that it fails.
if key != nil {
b := append([]byte(nil), bufEncoded.Bytes()...)
i := rand.Intn(len(b))
b[i]++
bufDecoded.Reset()
r, _, err = NewReader(bytes.NewReader(b), key)
if err == nil {
_, err = io.Copy(&bufDecoded, r)
}
if err == nil {
t.Errorf("got no error: expected error on data corruption in byte [%d] = %x", i, b[i])
}
}
// Change the key and verify that it fails.
newKey := integrityKey
if len(key) > 0 {
newKey = append([]byte{}, key...)
newKey[rand.Intn(len(newKey))]++
}
bufDecoded.Reset()
r, _, err = NewReader(bytes.NewReader(bufEncoded.Bytes()), newKey)
if err == nil {
_, err = io.Copy(&bufDecoded, r)
}
if err != compressio.ErrHashMismatch {
t.Errorf("got error: %v, expected ErrHashMismatch on key mismatch", err)
}
})
}
})
}
@@ -198,7 +215,7 @@ func benchmark(b *testing.B, size int, write bool, compressible bool) {
var stateBuf bytes.Buffer
writeState := func() {
stateBuf.Reset()
w, err := NewWriter(&stateBuf, key, nil)
w, err := NewWriter(&stateBuf, key, Options{Compression: CompressionLevelFlateBestSpeed}.WriteToMetadata(map[string]string{}))
if err != nil {
b.Fatalf("error creating writer: %v", err)
}
+42 -1
View File
@@ -16,12 +16,14 @@ package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/google/subcommands"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/state/statefile"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
@@ -36,6 +38,7 @@ const checkpointFileName = "checkpoint.img"
type Checkpoint struct {
imagePath string
leaveRunning bool
compression CheckpointCompression
}
// Name implements subcommands.Command.Name.
@@ -58,6 +61,7 @@ func (*Checkpoint) Usage() string {
func (c *Checkpoint) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.imagePath, "image-path", "", "directory path to saved container image")
f.BoolVar(&c.leaveRunning, "leave-running", false, "restart the container after checkpointing")
f.Var(newCheckpointCompressionValue(statefile.CompressionLevelFlateBestSpeed, &c.compression), "compression", "compress checkpoint image on disk. Values: none|flate-best-speed.")
// Unimplemented flags necessary for compatibility with docker.
var wp string
@@ -97,7 +101,7 @@ func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...any) su
}
defer file.Close()
if err := cont.Checkpoint(file); err != nil {
if err := cont.Checkpoint(file, statefile.Options{Compression: c.compression.Level()}); err != nil {
util.Fatalf("checkpoint failed: %v", err)
}
@@ -156,3 +160,40 @@ func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...any) su
return subcommands.ExitSuccess
}
// CheckpointCompression represents checkpoint image writer behavior. The
// default behavior is to compress because the default behavior used to be to
// always compress.
type CheckpointCompression statefile.CompressionLevel
func newCheckpointCompressionValue(val statefile.CompressionLevel, p *CheckpointCompression) *CheckpointCompression {
*p = CheckpointCompression(val)
return (*CheckpointCompression)(p)
}
// Set implements flag.Value.
func (g *CheckpointCompression) Set(v string) error {
t, err := statefile.CompressionLevelFromString(v)
if err != nil {
return fmt.Errorf("invalid checkpoint compression type %q", v)
}
*g = CheckpointCompression(t)
return nil
}
// Get implements flag.Getter.
func (g *CheckpointCompression) Get() any {
return *g
}
// String implements flag.Value.
func (g CheckpointCompression) String() string {
return string(g)
}
// Level returns corresponding statefile.CompressionLevel value.
func (g CheckpointCompression) Level() statefile.CompressionLevel {
return statefile.CompressionLevel(g)
}
+2
View File
@@ -24,6 +24,7 @@ go_library(
"//pkg/sentry/control",
"//pkg/sentry/pgalloc",
"//pkg/sighandling",
"//pkg/state/statefile",
"//pkg/sync",
"//runsc/boot",
"//runsc/cgroup",
@@ -80,6 +81,7 @@ go_test(
"//pkg/sentry/seccheck",
"//pkg/sentry/seccheck/points:points_go_proto",
"//pkg/sentry/seccheck/sinks/remote/test",
"//pkg/state/statefile",
"//pkg/sync",
"//pkg/test/testutil",
"//pkg/unet",
+3 -2
View File
@@ -39,6 +39,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sighandling"
"gvisor.dev/gvisor/pkg/state/statefile"
"gvisor.dev/gvisor/runsc/boot"
"gvisor.dev/gvisor/runsc/cgroup"
"gvisor.dev/gvisor/runsc/config"
@@ -691,12 +692,12 @@ func (c *Container) ForwardSignals(pid int32, fgProcess bool) func() {
// Checkpoint sends the checkpoint call to the container.
// The statefile will be written to f, the file at the specified image-path.
func (c *Container) Checkpoint(f *os.File) error {
func (c *Container) Checkpoint(f *os.File, options statefile.Options) error {
log.Debugf("Checkpoint container, cid: %s", c.ID)
if err := c.requireStatus("checkpoint", Created, Running, Paused); err != nil {
return err
}
return c.Sandbox.Checkpoint(c.ID, f)
return c.Sandbox.Checkpoint(c.ID, f, options)
}
// Pause suspends the container and its kernel.
+3 -2
View File
@@ -40,6 +40,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/state/statefile"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/boot"
@@ -1076,7 +1077,7 @@ func TestCheckpointRestore(t *testing.T) {
}
// Checkpoint running container; save state into new file.
if err := cont.Checkpoint(file); err != nil {
if err := cont.Checkpoint(file, statefile.Options{Compression: statefile.CompressionLevelFlateBestSpeed}); err != nil {
t.Fatalf("error checkpointing container to empty file: %v", err)
}
defer os.RemoveAll(imagePath)
@@ -1255,7 +1256,7 @@ func TestUnixDomainSockets(t *testing.T) {
}
// Checkpoint running container; save state into new file.
if err := cont.Checkpoint(file); err != nil {
if err := cont.Checkpoint(file, statefile.Options{Compression: statefile.CompressionLevelFlateBestSpeed}); err != nil {
t.Fatalf("error checkpointing container to empty file: %v", err)
}
+1
View File
@@ -32,6 +32,7 @@ go_library(
"//pkg/sentry/control",
"//pkg/sentry/platform",
"//pkg/sentry/seccheck",
"//pkg/state/statefile",
"//pkg/sync",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
+4 -2
View File
@@ -45,6 +45,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/seccheck"
"gvisor.dev/gvisor/pkg/state/statefile"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/urpc"
"gvisor.dev/gvisor/runsc/boot"
@@ -1217,9 +1218,10 @@ func (s *Sandbox) SignalProcess(cid string, pid int32, sig unix.Signal, fgProces
// Checkpoint sends the checkpoint call for a container in the sandbox.
// The statefile will be written to f.
func (s *Sandbox) Checkpoint(cid string, f *os.File) error {
log.Debugf("Checkpoint sandbox %q", s.ID)
func (s *Sandbox) Checkpoint(cid string, f *os.File, options statefile.Options) error {
log.Debugf("Checkpoint sandbox %q, options %+v", s.ID, options)
opt := control.SaveOpts{
Metadata: options.WriteToMetadata(map[string]string{}),
FilePayload: urpc.FilePayload{
Files: []*os.File{f},
},