mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
606 lines
20 KiB
Go
606 lines
20 KiB
Go
// Copyright 2026 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 boot
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"golang.org/x/sys/unix"
|
|
"google.golang.org/protobuf/proto"
|
|
"gvisor.dev/gvisor/pkg/cleanup"
|
|
"gvisor.dev/gvisor/pkg/context"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/fd"
|
|
"gvisor.dev/gvisor/pkg/hostarch"
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
"gvisor.dev/gvisor/pkg/sentry/checkpoint"
|
|
"gvisor.dev/gvisor/pkg/sentry/fscheckpoint"
|
|
fspb "gvisor.dev/gvisor/pkg/sentry/fscheckpoint/fscheckpoint_proto_go_proto"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
|
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
|
"gvisor.dev/gvisor/pkg/sentry/state/checkpointfiles"
|
|
"gvisor.dev/gvisor/pkg/sentry/state/stateio"
|
|
"gvisor.dev/gvisor/pkg/sentry/state/stateipc"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
"gvisor.dev/gvisor/pkg/unet"
|
|
"gvisor.dev/gvisor/pkg/urpc"
|
|
"gvisor.dev/gvisor/runsc/specutils"
|
|
"gvisor.dev/gvisor/runsc/version"
|
|
)
|
|
|
|
const (
|
|
annotationFSCheckpointPrefix = "dev.gvisor.internal.fscheckpoint."
|
|
|
|
// annotationFSCheckpointEnable indicates whether files under /proc/gvisor
|
|
// should be present in the container to allow the workload to trigger a
|
|
// filesystem checkpoint.
|
|
annotationFSCheckpointEnable = annotationFSCheckpointPrefix + "enable"
|
|
|
|
// annotationFSCheckpointPath is the path to the directory where the
|
|
// filesystem checkpoint files will be created. When present, it allows for
|
|
// the workload running inside to trigger a filesystem checkpoint without
|
|
// having to use the runsc CLI.
|
|
annotationFSCheckpointPath = annotationFSCheckpointPrefix + "path"
|
|
|
|
// annotationFSCheckpointResume indicates whether the sandbox should
|
|
// continue running after filesystem checkpoint saving triggered via
|
|
// /proc/gvisor. Optional, defaults to false.
|
|
annotationFSCheckpointResume = annotationFSCheckpointPrefix + "resume"
|
|
|
|
// annotationFSCheckpointDirect indicates whether filesystem checkpoint
|
|
// I/Os triggered via /proc/gvisor should use O_DIRECT. Optional, defaults
|
|
// to false.
|
|
annotationFSCheckpointDirect = annotationFSCheckpointPrefix + "direct"
|
|
|
|
// annotationFSCheckpointPaths is a comma-separated list of paths inside the
|
|
// containers to save. Optional.
|
|
annotationFSCheckpointPaths = annotationFSCheckpointPrefix + "paths"
|
|
)
|
|
|
|
// GetAnnotationFSCheckpointPath returns the filesystem checkpoint path
|
|
// specified in the container annotation. Return empty string if no annotation
|
|
// is specified.
|
|
func GetAnnotationFSCheckpointPath(spec *specs.Spec) string {
|
|
return spec.Annotations[annotationFSCheckpointPath]
|
|
}
|
|
|
|
// GetAnnotationFSCheckpointDirect returns true if filesystem checkpoint I/O
|
|
// controlled by the containing annotation should use O_DIRECT.
|
|
func GetAnnotationFSCheckpointDirect(spec *specs.Spec) bool {
|
|
return specutils.AnnotationToBool(spec, annotationFSCheckpointDirect)
|
|
}
|
|
|
|
// FSSave implements kernel.Saver.FSSave.
|
|
func (l *Loader) FSSave() error {
|
|
l.mu.Lock()
|
|
fsSaveFDs := l.fsSaveFDs
|
|
l.fsSaveFDs = nil
|
|
useCheckpointGofer := l.fsSaveCheckpointGofer
|
|
l.mu.Unlock()
|
|
if len(fsSaveFDs) == 0 {
|
|
return linuxerr.ENXIO
|
|
}
|
|
paths, err := parseFSCheckpointPaths(l.root.spec.Annotations[annotationFSCheckpointPaths])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
args := FSSaveArgs{
|
|
ExitAfterSaving: !specutils.AnnotationToBool(l.root.spec, annotationFSCheckpointResume),
|
|
Paths: paths,
|
|
}
|
|
args.FilePayload.Files = fd.ReleaseToFiles(fsSaveFDs, "fs-checkpoint")
|
|
args.UseCheckpointGofer = useCheckpointGofer
|
|
opts := kernel.FSSaveOpts{
|
|
RunscVersion: version.Version(),
|
|
Paths: paths,
|
|
}
|
|
if err := setKernelFSSaveOptsFiles(&args, &opts); err != nil {
|
|
return err
|
|
}
|
|
return l.k.FSSave(context.Background(), &opts)
|
|
}
|
|
|
|
func parseFSCheckpointPaths(val string) ([]checkpoint.ResourceID, error) {
|
|
if val == "" {
|
|
return nil, nil
|
|
}
|
|
var paths []checkpoint.ResourceID
|
|
for _, part := range strings.Split(val, ",") {
|
|
part = strings.TrimSpace(part)
|
|
if part == "" {
|
|
continue
|
|
}
|
|
subparts := strings.SplitN(part, ":", 2)
|
|
if len(subparts) == 1 {
|
|
p := strings.TrimSpace(subparts[0])
|
|
if p == "" {
|
|
return nil, fmt.Errorf("empty path in fscheckpoint paths: %q", val)
|
|
}
|
|
paths = append(paths, checkpoint.ResourceID{Path: p})
|
|
} else {
|
|
c := strings.TrimSpace(subparts[0])
|
|
p := strings.TrimSpace(subparts[1])
|
|
if p == "" {
|
|
return nil, fmt.Errorf("empty path in fscheckpoint paths: %q", val)
|
|
}
|
|
paths = append(paths, checkpoint.ResourceID{ContainerName: c, Path: p})
|
|
}
|
|
}
|
|
return paths, nil
|
|
}
|
|
|
|
func convertToKernelFSSaveOpts(args *FSSaveArgs) (kernel.FSSaveOpts, error) {
|
|
opts := kernel.FSSaveOpts{
|
|
RunscVersion: version.Version(),
|
|
ExitAfterSaving: args.ExitAfterSaving,
|
|
Paths: args.Paths,
|
|
}
|
|
if err := setKernelFSSaveOptsFiles(args, &opts); err != nil {
|
|
return kernel.FSSaveOpts{}, err
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
func setKernelFSSaveOptsFiles(args *FSSaveArgs, opts *kernel.FSSaveOpts) error {
|
|
if args.UseCheckpointGofer {
|
|
return setKernelFSSaveOptsFilesForCheckpointGofer(args, opts)
|
|
}
|
|
return setKernelFSSaveOptsFilesForLocalCheckpoint(args, opts)
|
|
}
|
|
|
|
func setKernelFSSaveOptsFilesForLocalCheckpoint(args *FSSaveArgs, opts *kernel.FSSaveOpts) error {
|
|
if len(args.FilePayload.Files) != 4 {
|
|
return fmt.Errorf("got %d files, want 4", len(args.FilePayload.Files))
|
|
}
|
|
manifestFile, err := args.ReleaseFD(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
multiTarFile, err := args.ReleaseFD(1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pagesMetadataFile, err := args.ReleaseFD(2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pagesFile, err := args.ReleaseFD(3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts.ManifestFile = stateio.NewBufioWriteCloser(manifestFile)
|
|
opts.MultiTarFile = stateio.NewBufioWriteCloser(multiTarFile)
|
|
opts.PagesMetadataFile = stateio.NewBufioWriteCloser(pagesMetadataFile)
|
|
opts.PagesFile = stateio.NewPagesFileFDWriterDefault(int32(pagesFile.Release()))
|
|
return nil
|
|
}
|
|
|
|
func setKernelFSSaveOptsFilesForCheckpointGofer(args *FSSaveArgs, opts *kernel.FSSaveOpts) error {
|
|
clientFD, err := unix.Dup(int(args.Files[0].Fd()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to dup checkpoint gofer client FD: %w", err)
|
|
}
|
|
clientSock, err := unet.NewSocket(clientFD)
|
|
if err != nil {
|
|
unix.Close(clientFD)
|
|
return fmt.Errorf("failed to create unet.Socket for checkpoint gofer client FD: %w", err)
|
|
}
|
|
afc, err := stateipc.NewAsyncFileClient(urpc.NewClient(clientSock) /* transfers ownership */)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create stateipc client: %w", err)
|
|
}
|
|
defer afc.DecRef()
|
|
|
|
manifestFileAsync, err := afc.OpenWrite(checkpointfiles.FSCheckpointManifestFileName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open manifest file: %w", err)
|
|
}
|
|
manifestFile, err := stateio.NewBufWriter(manifestFileAsync /* transfers ownership */, 2<<20 /* size = 2 MiB */)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to buffer manifest file: %w", err)
|
|
}
|
|
closeCleanup := cleanup.Make(func() { manifestFile.Close() })
|
|
defer closeCleanup.Clean()
|
|
|
|
multiTarFileAsync, err := afc.OpenWrite(checkpointfiles.FSCheckpointMultiTarFileName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open multi-tar file: %w", err)
|
|
}
|
|
multiTarFile, err := stateio.NewBufWriter(multiTarFileAsync /* transfers ownership */, 8<<20 /* size = 8 MiB */)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to buffer multi-tar file: %w", err)
|
|
}
|
|
closeCleanup.Add(func() { multiTarFile.Close() })
|
|
|
|
pagesMetadataFileAsync, err := afc.OpenWrite(checkpointfiles.PagesMetadataFileName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open pages metadata file: %w", err)
|
|
}
|
|
pagesMetadataFile, err := stateio.NewBufWriter(pagesMetadataFileAsync /* transfers ownership */, 8<<20 /* size = 8 MiB */)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to buffer pages metadata file: %w", err)
|
|
}
|
|
closeCleanup.Add(func() { pagesMetadataFile.Close() })
|
|
|
|
pagesFile, err := afc.OpenWrite(checkpointfiles.PagesFileName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open pages file: %w", err)
|
|
}
|
|
|
|
closeCleanup.Release()
|
|
opts.ManifestFile = manifestFile
|
|
opts.MultiTarFile = multiTarFile
|
|
opts.PagesMetadataFile = pagesMetadataFile
|
|
opts.PagesFile = pagesFile
|
|
return nil
|
|
}
|
|
|
|
// fsRestore holds the state of a filesystem restore.
|
|
type fsRestore struct {
|
|
wg sync.WaitGroup
|
|
|
|
// immutable
|
|
getPagesMetadata func() ([]byte, error)
|
|
getMultiTar func() ([]byte, error)
|
|
|
|
// immutable after wg.Wait()
|
|
manifestErr error
|
|
apfl *pgalloc.AsyncPagesFileLoad
|
|
mfs map[checkpoint.ResourceID]*fscheckpoint.MemoryFile
|
|
tmpfs map[checkpoint.ResourceID]*fscheckpoint.Tmpfs
|
|
|
|
waitMu sync.Mutex
|
|
waitMap map[string]*fsRestoreContainer // key is container ID
|
|
}
|
|
|
|
type fsRestoreContainer struct {
|
|
// These fields are protected by fsRestore.waitMu.
|
|
err error
|
|
asyncLoads int // number of MemoryFiles currently in async page loading
|
|
cond sync.Cond
|
|
}
|
|
|
|
// fsRestoreOpts holds options to startFSRestore.
|
|
type fsRestoreOpts struct {
|
|
// These correspond to files specified by the fscheckpoint package, and are
|
|
// all required.
|
|
ManifestFile io.ReadCloser
|
|
MultiTarFile io.ReadCloser
|
|
PagesMetadataFile io.ReadCloser
|
|
PagesFile stateio.AsyncReader
|
|
}
|
|
|
|
func makeFSRestoreOpts(args *Args) (fsRestoreOpts, error) {
|
|
if args.FSRestoreCheckpointGofer {
|
|
return makeFSRestoreOptsForCheckpointGofer(args)
|
|
}
|
|
return makeFSRestoreOptsForLocalCheckpoint(args)
|
|
}
|
|
|
|
func makeFSRestoreOptsForLocalCheckpoint(args *Args) (fsRestoreOpts, error) {
|
|
if len(args.FSRestoreFDs) != 4 {
|
|
return fsRestoreOpts{}, fmt.Errorf("got %d files in -fs-restore-fds, want 4", len(args.FSRestoreFDs))
|
|
}
|
|
return fsRestoreOpts{
|
|
ManifestFile: stateio.NewBufioReadCloser(args.FSRestoreFDs[0].ReleaseToFile(checkpointfiles.FSCheckpointManifestFileName)),
|
|
MultiTarFile: stateio.NewBufioReadCloser(args.FSRestoreFDs[1].ReleaseToFile(checkpointfiles.FSCheckpointMultiTarFileName)),
|
|
PagesMetadataFile: stateio.NewBufioReadCloser(args.FSRestoreFDs[2].ReleaseToFile(checkpointfiles.PagesMetadataFileName)),
|
|
PagesFile: stateio.NewPagesFileFDReaderDefault(int32(args.FSRestoreFDs[3].Release())),
|
|
}, nil
|
|
}
|
|
|
|
func makeFSRestoreOptsForCheckpointGofer(args *Args) (fsRestoreOpts, error) {
|
|
if len(args.FSRestoreFDs) != 1 {
|
|
return fsRestoreOpts{}, fmt.Errorf("got %d files in -fs-restore-fds, want 1", len(args.FSRestoreFDs))
|
|
}
|
|
clientFD := args.FSRestoreFDs[0].Release()
|
|
clientSock, err := unet.NewSocket(clientFD)
|
|
if err != nil {
|
|
unix.Close(clientFD)
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to create unet.Socket for checkpoint gofer client FD: %w", err)
|
|
}
|
|
afc, err := stateipc.NewAsyncFileClient(urpc.NewClient(clientSock) /* transfers ownership */)
|
|
if err != nil {
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to create stateipc client: %w", err)
|
|
}
|
|
defer afc.DecRef()
|
|
|
|
manifestFileAsync, err := afc.OpenRead(checkpointfiles.FSCheckpointManifestFileName)
|
|
if err != nil {
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to open manifest file: %w", err)
|
|
}
|
|
manifestFile, err := stateio.NewBufReader(manifestFileAsync /* transfers ownership */, 2<<20 /* size = 2 MiB */)
|
|
if err != nil {
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to buffer manifest file: %w", err)
|
|
}
|
|
closeCleanup := cleanup.Make(func() { manifestFile.Close() })
|
|
defer closeCleanup.Clean()
|
|
|
|
multiTarFileAsync, err := afc.OpenRead(checkpointfiles.FSCheckpointMultiTarFileName)
|
|
if err != nil {
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to open multi-tar file: %w", err)
|
|
}
|
|
multiTarFile, err := stateio.NewBufReader(multiTarFileAsync /* transfers ownership */, 8<<20 /* size = 8 MiB */)
|
|
if err != nil {
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to buffer multi-tar file: %w", err)
|
|
}
|
|
closeCleanup.Add(func() { multiTarFile.Close() })
|
|
|
|
pagesMetadataFileAsync, err := afc.OpenRead(checkpointfiles.PagesMetadataFileName)
|
|
if err != nil {
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to open pages metadata file: %w", err)
|
|
}
|
|
pagesMetadataFile, err := stateio.NewBufReader(pagesMetadataFileAsync /* transfers ownership */, 8<<20 /* size = 8 MiB */)
|
|
if err != nil {
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to buffer pages metadata file: %w", err)
|
|
}
|
|
closeCleanup.Add(func() { pagesMetadataFile.Close() })
|
|
|
|
pagesFile, err := afc.OpenRead(checkpointfiles.PagesFileName)
|
|
if err != nil {
|
|
return fsRestoreOpts{}, fmt.Errorf("failed to open pages file: %w", err)
|
|
}
|
|
|
|
closeCleanup.Release()
|
|
return fsRestoreOpts{
|
|
ManifestFile: manifestFile,
|
|
MultiTarFile: multiTarFile,
|
|
PagesMetadataFile: pagesMetadataFile,
|
|
PagesFile: pagesFile,
|
|
}, nil
|
|
}
|
|
|
|
// startFSRestore takes ownership of resources in opts.
|
|
func startFSRestore(opts *fsRestoreOpts) (*fsRestore, error) {
|
|
fsr := &fsRestore{
|
|
mfs: make(map[checkpoint.ResourceID]*fscheckpoint.MemoryFile),
|
|
tmpfs: make(map[checkpoint.ResourceID]*fscheckpoint.Tmpfs),
|
|
waitMap: make(map[string]*fsRestoreContainer),
|
|
}
|
|
|
|
// TODO: NOLINT - Currently we read the whole pages metadata file into a
|
|
// []byte, then pass pieces of that []byte to MemoryFile construction. This
|
|
// is necessary because opts.PagesMetadataFile is io.Reader (read
|
|
// sequentially), and tmpfs filesystems and their private MemoryFiles may
|
|
// be restored in a different order than checkpoint order (disk-backed
|
|
// filestore files are not available until container creation).
|
|
//
|
|
// We could make opts.PagesMetadataFile io.ReaderAt to avoid this copy.
|
|
// However, when the multi-tar file is accessed via stateio.AsyncReader,
|
|
// this requires an implementation of io.ReaderAt that wraps
|
|
// stateio.AsyncReader, akin to stateio.BufReader. AsyncReader already
|
|
// supports random reads, but has a fixed maximum parallelism per
|
|
// AsyncReader that would need to be shared between readers. Furthermore,
|
|
// BufReader asynchronously fills its buffer with reads to minimize
|
|
// latency; our io.ReaderAt implementation would need to do something
|
|
// comparable to avoid regressions.
|
|
//
|
|
// Alternatively, we could implement io.ReaderAt by asynchronously
|
|
// buffering the whole file in memory, which is probably better overall
|
|
// (equivalent to what we are doing now, but permits reading parts of the
|
|
// file that have been read before the whole file is read) but requires
|
|
// adding a stateio.AsyncReader method to get file size.
|
|
//
|
|
// All of the above also applies to the multi-tar file.
|
|
readOnce := func(desc string, optsR *io.ReadCloser) func() ([]byte, error) {
|
|
r := *optsR
|
|
*optsR = nil
|
|
f := sync.OnceValues(func() ([]byte, error) {
|
|
timeStart := time.Now()
|
|
data, err := io.ReadAll(r)
|
|
dur := time.Since(timeStart)
|
|
// Close r immediately to release any memory used for buffering.
|
|
r.Close()
|
|
r = nil
|
|
if err == nil {
|
|
log.Infof("Read filesystem checkpoint %s (%d bytes) in %s", desc, len(data), dur)
|
|
}
|
|
return data, err
|
|
})
|
|
// Start reading immediately.
|
|
go f()
|
|
return f
|
|
}
|
|
fsr.getPagesMetadata = readOnce("pages metadata file", &opts.PagesMetadataFile)
|
|
fsr.getMultiTar = readOnce("multi-tar file", &opts.MultiTarFile)
|
|
|
|
// Read and handle the manifest in parallel.
|
|
fsr.manifestErr = fmt.Errorf("loading manifest panicked")
|
|
fsr.wg.Go(func() {
|
|
defer func() {
|
|
if opts.ManifestFile != nil {
|
|
opts.ManifestFile.Close()
|
|
opts.ManifestFile = nil
|
|
}
|
|
if opts.PagesFile != nil {
|
|
opts.PagesFile.Close()
|
|
opts.PagesFile = nil
|
|
}
|
|
}()
|
|
fsr.manifestErr = func() error {
|
|
var manifest fscheckpoint.Manifest
|
|
timeStart := time.Now()
|
|
manifestData, err := io.ReadAll(opts.ManifestFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read manifest: %w", err)
|
|
}
|
|
var pb fspb.Manifest
|
|
if err := proto.Unmarshal(manifestData, &pb); err != nil {
|
|
return fmt.Errorf("failed to decode proto manifest: %w", err)
|
|
}
|
|
manifest = fscheckpoint.FromProto(&pb)
|
|
log.Infof("Read filesystem checkpoint manifest in %s", time.Since(timeStart))
|
|
if manifest.Version > 1 {
|
|
return fmt.Errorf("unsupported filesystem checkpoint version: %d", manifest.Version)
|
|
}
|
|
log.Infof("Restoring from proto manifest (version %d) created by runsc version %q", manifest.Version, manifest.RunscVersion)
|
|
if manifest.PageSize != hostarch.PageSize {
|
|
return fmt.Errorf("filesystem checkpoint page size %d does not match current page size %d", manifest.PageSize, hostarch.PageSize)
|
|
}
|
|
if endian := hostarch.EndianString(); manifest.Endian != endian {
|
|
return fmt.Errorf("filesystem checkpoint endianness %q does not match current endianness %q", manifest.Endian, endian)
|
|
}
|
|
|
|
apfl, err := pgalloc.StartAsyncPagesFileLoad(opts.PagesFile /* transfers ownership */, func(err error) {
|
|
if err != nil {
|
|
log.Warningf("Failed to load filesystem checkpoint pages: %v", err)
|
|
} else if log.IsLogging(log.Debug) {
|
|
log.Debugf("Finished loading filesystem checkpoint pages")
|
|
}
|
|
}, nil)
|
|
opts.PagesFile = nil
|
|
if err != nil {
|
|
return fmt.Errorf("failed to start async page loading: %w", err)
|
|
}
|
|
fsr.apfl = apfl
|
|
for i := range manifest.MemoryFiles {
|
|
mmf := &manifest.MemoryFiles[i]
|
|
fsr.mfs[mmf.ResourceID] = mmf
|
|
}
|
|
for i := range manifest.Tmpfs {
|
|
mt := &manifest.Tmpfs[i]
|
|
fsr.tmpfs[mt.ResourceID] = mt
|
|
}
|
|
// Note that apfl.MemoryFilesDone() will never be called. This is
|
|
// because if a container fails and is restarted, we also want to
|
|
// restore the filesystem of the restarted container, so we need to
|
|
// keep filesystem restore operational indefinitely.
|
|
return nil
|
|
}()
|
|
})
|
|
|
|
return fsr, nil
|
|
}
|
|
|
|
// +checklocks:fsr.waitMu
|
|
func (fsr *fsRestore) ensureContainer(cid string) *fsRestoreContainer {
|
|
c := fsr.waitMap[cid]
|
|
if c == nil {
|
|
c = &fsRestoreContainer{}
|
|
c.cond.L = &fsr.waitMu
|
|
fsr.waitMap[cid] = c
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c *fsRestoreContainer) setError(err error) error {
|
|
if c.err == nil && err != nil {
|
|
c.err = err
|
|
c.cond.Broadcast()
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (fsr *fsRestore) memoryFileLoadArgs(id checkpoint.ResourceID, cid string) (io.Reader, uint64, func(error), error) {
|
|
if fsr == nil {
|
|
return nil, 0, func(error) {}, nil
|
|
}
|
|
|
|
fsr.wg.Wait()
|
|
if fsr.manifestErr != nil {
|
|
return nil, 0, nil, fsr.manifestErr
|
|
}
|
|
mmf := fsr.mfs[id]
|
|
if mmf == nil {
|
|
return nil, 0, func(error) {}, nil
|
|
}
|
|
pagesMetadata, err := fsr.getPagesMetadata()
|
|
|
|
fsr.waitMu.Lock()
|
|
defer fsr.waitMu.Unlock()
|
|
c := fsr.ensureContainer(cid)
|
|
if mmf.PagesMetadataEnd > uint64(len(pagesMetadata)) {
|
|
if err != nil {
|
|
return nil, 0, nil, c.setError(fmt.Errorf("failed to read pages metadata: %w", err))
|
|
}
|
|
return nil, 0, nil, c.setError(fmt.Errorf("MemoryFile %q has pages metadata range [%d, %d) beyond pages metadata file size %d", mmf.ResourceID, mmf.PagesMetadataStart, mmf.PagesMetadataEnd, len(pagesMetadata)))
|
|
}
|
|
c.asyncLoads++
|
|
return bytes.NewReader(pagesMetadata[mmf.PagesMetadataStart:mmf.PagesMetadataEnd]), mmf.PagesStart, func(err error) {
|
|
fsr.waitMu.Lock()
|
|
defer fsr.waitMu.Unlock()
|
|
c.asyncLoads--
|
|
switch {
|
|
case err != nil:
|
|
if c.err == nil {
|
|
c.err = err
|
|
}
|
|
fallthrough
|
|
case c.asyncLoads == 0:
|
|
c.cond.Broadcast()
|
|
}
|
|
}, nil
|
|
}
|
|
|
|
func (fsr *fsRestore) tmpfsSourceTar(id checkpoint.ResourceID, cid string) (io.ReadCloser, error) {
|
|
if fsr == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
fsr.wg.Wait()
|
|
if fsr.manifestErr != nil {
|
|
return nil, fsr.manifestErr
|
|
}
|
|
mt := fsr.tmpfs[id]
|
|
if mt == nil {
|
|
return nil, nil
|
|
}
|
|
multiTar, err := fsr.getMultiTar()
|
|
|
|
fsr.waitMu.Lock()
|
|
defer fsr.waitMu.Unlock()
|
|
if mt.TarEnd <= uint64(len(multiTar)) {
|
|
return io.NopCloser(bytes.NewReader(multiTar[mt.TarStart:mt.TarEnd])), nil
|
|
}
|
|
c := fsr.ensureContainer(cid)
|
|
if err != nil {
|
|
return nil, c.setError(fmt.Errorf("failed to read tar archive: %w", err))
|
|
}
|
|
return nil, c.setError(fmt.Errorf("tmpfs %q has tar range [%d, %d) beyond multi-tar file size %d", mt.ResourceID, mt.TarStart, mt.TarEnd, len(multiTar)))
|
|
}
|
|
|
|
// wait blocks until either all filesystems have been restored for the
|
|
// container with the given ID, or an error occurs while restoring filesystems
|
|
// for that container.
|
|
func (fsr *fsRestore) wait(cid string) error {
|
|
if fsr == nil {
|
|
return fmt.Errorf("filesystem restore is not enabled")
|
|
}
|
|
fsr.wg.Wait()
|
|
if fsr.manifestErr != nil {
|
|
return fsr.manifestErr
|
|
}
|
|
fsr.waitMu.Lock()
|
|
defer fsr.waitMu.Unlock()
|
|
c := fsr.waitMap[cid]
|
|
if c == nil {
|
|
return fmt.Errorf("no filesystems restored for container %s", cid)
|
|
}
|
|
for {
|
|
if c.err != nil {
|
|
return c.err
|
|
}
|
|
if c.asyncLoads == 0 {
|
|
return nil
|
|
}
|
|
c.cond.Wait()
|
|
}
|
|
}
|