Add container name to unique path keys

This is to allow multi-containers to be saved/restored
without conflicting "unique" IDs.

Updates #1956

PiperOrigin-RevId: 601231193
This commit is contained in:
Fabricio Voznika
2024-01-24 14:16:44 -08:00
committed by gVisor bot
parent 8555e41398
commit 35dab382a6
11 changed files with 77 additions and 43 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ type filesystem struct {
type InternalFilesystemOptions struct {
// If UniqueID is non-empty, it is an opaque string used to reassociate the
// filesystem with a new image FD during restoration from checkpoint.
UniqueID string
UniqueID vfs.RestoreID
}
// Name implements vfs.FilesystemType.Name.
+1 -1
View File
@@ -38,7 +38,7 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
if fdmapv == nil {
return fmt.Errorf("no image FD map available")
}
fdmap := fdmapv.(map[string]int)
fdmap := fdmapv.(map[vfs.RestoreID]int)
fd, ok := fdmap[fs.iopts.UniqueID]
if !ok {
return fmt.Errorf("no image FD available for filesystem with unique ID %q", fs.iopts.UniqueID)
+1 -1
View File
@@ -363,7 +363,7 @@ const (
type InternalFilesystemOptions struct {
// If UniqueID is non-empty, it is an opaque string used to reassociate the
// filesystem with a new server FD during restoration from checkpoint.
UniqueID string
UniqueID vfs.RestoreID
// If LeakConnection is true, do not close the connection to the server
// when the Filesystem is released. This is necessary for deployments in
+3 -3
View File
@@ -37,7 +37,7 @@ type savedDentryRW struct {
// PrepareSave implements vfs.FilesystemImplSaveRestoreExtension.PrepareSave.
func (fs *filesystem) PrepareSave(ctx context.Context) error {
if len(fs.iopts.UniqueID) == 0 {
if len(fs.iopts.UniqueID.Path) == 0 {
return fmt.Errorf("gofer.filesystem with no UniqueID cannot be saved")
}
@@ -176,10 +176,10 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
if fdmapv == nil {
return fmt.Errorf("no server FD map available")
}
fdmap := fdmapv.(map[string]int)
fdmap := fdmapv.(map[vfs.RestoreID]int)
fd, ok := fdmap[fs.iopts.UniqueID]
if !ok {
return fmt.Errorf("no server FD available for filesystem with unique ID %q", fs.iopts.UniqueID)
return fmt.Errorf("no server FD available for filesystem with unique ID %+v, map: %v", fs.iopts.UniqueID, fdmap)
}
fs.opts.fd = fd
fs.inoByKey = make(map[inoKey]uint64)
+2 -2
View File
@@ -48,7 +48,7 @@ func (fs *filesystem) PrepareSave(ctx context.Context) error {
if mfmapv == nil {
return fmt.Errorf("CtxFilesystemMemoryFileMap was not provided")
}
mfmap := mfmapv.(map[string]*pgalloc.MemoryFile)
mfmap := mfmapv.(map[vfs.RestoreID]*pgalloc.MemoryFile)
mfmap[fs.uniqueID] = fs.mf
return nil
}
@@ -63,7 +63,7 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
if mfmapv == nil {
return fmt.Errorf("CtxFilesystemMemoryFileMap was not provided")
}
mfmap := mfmapv.(map[string]*pgalloc.MemoryFile)
mfmap := mfmapv.(map[vfs.RestoreID]*pgalloc.MemoryFile)
mf, ok := mfmap[fs.uniqueID]
if !ok {
return fmt.Errorf("memory file for %q not found in CtxFilesystemMemoryFileMap", fs.uniqueID)
+4 -4
View File
@@ -72,7 +72,7 @@ type filesystem struct {
// uniqueID is an opaque string used to reassociate the filesystem with its
// private MemoryFile during checkpoint and restore.
uniqueID string
uniqueID vfs.RestoreID
// mfp is used to provide mf, when privateMF == false. This is required to
// re-provide mf on restore. mfp is immutable.
@@ -159,7 +159,7 @@ type FilesystemOpts struct {
// If UniqueID is non-empty, it is an opaque string used to reassociate the
// filesystem with its private MemoryFile during checkpoint and restore.
UniqueID string
UniqueID vfs.RestoreID
}
// Default size limit mount option. It is immutable after initialization.
@@ -192,7 +192,7 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
}
mf := mfp.MemoryFile()
privateMF := false
uniqueID := ""
var uniqueID vfs.RestoreID
rootFileType := uint16(linux.S_IFDIR)
disableDefaultSizeLimit := false
newFSType := vfs.FilesystemType(&fstype)
@@ -225,7 +225,7 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
allowXattrPrefix[xattr] = struct{}{}
}
}
if privateMF && uniqueID == "" {
if privateMF && len(uniqueID.Path) == 0 {
ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: privateMF requires uniqueID to be set")
return nil, nil, linuxerr.EINVAL
}
+5 -5
View File
@@ -505,10 +505,10 @@ func (k *Kernel) Init(args InitKernelArgs) error {
// +stateify savable
type privateMemoryFileMetadata struct {
owners []string
owners []vfs.RestoreID
}
func savePrivateMFs(ctx context.Context, w wire.Writer, mfsToSave map[string]*pgalloc.MemoryFile) error {
func savePrivateMFs(ctx context.Context, w wire.Writer, mfsToSave map[vfs.RestoreID]*pgalloc.MemoryFile) error {
var meta privateMemoryFileMetadata
// Generate the order in which private memory files are saved.
for fsID := range mfsToSave {
@@ -533,9 +533,9 @@ func loadPrivateMFs(ctx context.Context, r wire.Reader) error {
if _, err := state.Load(ctx, r, &meta); err != nil {
return err
}
var mfmap map[string]*pgalloc.MemoryFile
var mfmap map[vfs.RestoreID]*pgalloc.MemoryFile
if mfmapv := ctx.Value(vfs.CtxFilesystemMemoryFileMap); mfmapv != nil {
mfmap = mfmapv.(map[string]*pgalloc.MemoryFile)
mfmap = mfmapv.(map[vfs.RestoreID]*pgalloc.MemoryFile)
}
// Ensure that it is consistent with CtxFilesystemMemoryFileMap.
if len(mfmap) != len(meta.owners) {
@@ -578,7 +578,7 @@ func (k *Kernel) SaveTo(ctx context.Context, w wire.Writer) error {
}
// Capture all private memory files.
mfsToSave := make(map[string]*pgalloc.MemoryFile)
mfsToSave := make(map[vfs.RestoreID]*pgalloc.MemoryFile)
vfsCtx := context.WithValue(ctx, vfs.CtxFilesystemMemoryFileMap, mfsToSave)
// Prepare filesystems for saving. This must be done after
// invalidateUnsavableMappings(), since dropping memory mappings may
+16
View File
@@ -15,6 +15,7 @@
package vfs
import (
"fmt"
"sync/atomic"
"gvisor.dev/gvisor/pkg/context"
@@ -133,3 +134,18 @@ func (epi *epollInterest) afterLoad() {
// EpollInstance.ReadEvents() rechecks their readiness.
epi.waiter.NotifyEvent(waiter.EventMaskFromLinux(epi.mask))
}
// RestoreID is a unique ID that is used to identify resources between save/restore sessions.
// Example of resources are host files, gofer connection for mount points, etc.
//
// +stateify savable
type RestoreID struct {
// ContainerName is the name of the container that the resource belongs to.
ContainerName string
// Path is the path of the resource.
Path string
}
func (f RestoreID) String() string {
return fmt.Sprintf("%s:%s", f.ContainerName, f.Path)
}
+6 -2
View File
@@ -95,6 +95,8 @@ import (
type containerInfo struct {
cid string
containerName string
conf *config.Config
// spec is the base configuration for the root container.
@@ -328,6 +330,7 @@ func New(args Args) (*Loader, error) {
info := containerInfo{
cid: args.ID,
containerName: specutils.ContainerName(args.Spec),
conf: args.Conf,
spec: args.Spec,
goferMountConfs: args.GoferMountConfs,
@@ -899,6 +902,7 @@ func (l *Loader) startSubcontainer(spec *specs.Spec, conf *config.Config, cid st
info := &containerInfo{
cid: cid,
containerName: specutils.ContainerName(spec),
conf: conf,
spec: spec,
goferFDs: goferFDs,
@@ -973,7 +977,7 @@ func (l *Loader) startSubcontainer(spec *specs.Spec, conf *config.Config, cid st
func (l *Loader) createContainerProcess(info *containerInfo) (*kernel.ThreadGroup, *host.TTYFileDescription, error) {
// Create the FD map, which will set stdin, stdout, and stderr.
ctx := info.procArgs.NewContext(l.k)
fdTable, ttyFile, err := createFDTable(ctx, info.spec.Process.Terminal, info.stdioFDs, info.passFDs, info.spec.Process.User)
fdTable, ttyFile, err := createFDTable(ctx, info.spec.Process.Terminal, info.stdioFDs, info.passFDs, info.spec.Process.User, info.containerName)
if err != nil {
return nil, nil, fmt.Errorf("importing fds: %w", err)
}
@@ -1572,7 +1576,7 @@ func (l *Loader) ttyFromIDLocked(key execID) (*host.TTYFileDescription, error) {
return ep.tty, nil
}
func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD, passFDs []fdMapping, user specs.User) (*kernel.FDTable, *host.TTYFileDescription, error) {
func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD, passFDs []fdMapping, user specs.User, containerName string) (*kernel.FDTable, *host.TTYFileDescription, error) {
if len(stdioFDs) != 3 {
return nil, nil, fmt.Errorf("stdioFDs should contain exactly 3 FDs (stdin, stdout, and stderr), but %d FDs received", len(stdioFDs))
}
+31 -15
View File
@@ -402,7 +402,8 @@ type containerMounter struct {
containerID string
// sandboxID is the ID for the whole sandbox.
sandboxID string
sandboxID string
containerName string
// cgroupMounts is a map of cgroup mounts that can be reused across
// containers. Key is the cgroup controller name string.
@@ -434,6 +435,7 @@ func newContainerMounter(info *containerInfo, k *kernel.Kernel, hints *PodMountH
productName: productName,
containerID: info.procArgs.ContainerID,
sandboxID: sandboxID,
containerName: info.containerName,
cgroupMounts: cgroupMounts,
}
}
@@ -519,7 +521,10 @@ func (c *containerMounter) createMountNamespace(ctx context.Context, conf *confi
InternalMount: true,
Data: strings.Join(data, ","),
InternalData: gofer.InternalFilesystemOptions{
UniqueID: "/",
UniqueID: vfs.RestoreID{
ContainerName: c.containerName,
Path: "/",
},
},
},
}
@@ -532,7 +537,10 @@ func (c *containerMounter) createMountNamespace(ctx context.Context, conf *confi
InternalMount: true,
Data: fmt.Sprintf("ifd=%d", ioFD),
InternalData: erofs.InternalFilesystemOptions{
UniqueID: "/",
UniqueID: vfs.RestoreID{
ContainerName: c.containerName,
Path: "/",
},
},
},
}
@@ -645,7 +653,7 @@ func (c *containerMounter) configureOverlay(ctx context.Context, conf *config.Co
return nil, nil, fmt.Errorf("failed to create memory file for overlay: %v", err)
}
tmpfsOpts.MemoryFile = mf
tmpfsOpts.UniqueID = dst
tmpfsOpts.UniqueID = vfs.RestoreID{ContainerName: c.containerName, Path: dst}
}
upperOpts.GetFilesystemOptions.InternalData = tmpfsOpts
upper, err := c.k.VFS().MountDisconnected(ctx, creds, "" /* source */, tmpfs.Name, &upperOpts)
@@ -829,7 +837,7 @@ func (c *containerMounter) prepareMounts() ([]mountInfo, error) {
}
func (c *containerMounter) mountSubmount(ctx context.Context, spec *specs.Spec, conf *config.Config, mns *vfs.MountNamespace, creds *auth.Credentials, submount *mountInfo) (*vfs.Mount, error) {
fsName, opts, err := getMountNameAndOptions(spec, conf, submount, c.productName)
fsName, opts, err := getMountNameAndOptions(spec, conf, submount, c.productName, c.containerName)
if err != nil {
return nil, fmt.Errorf("mountOptions failed: %w", err)
}
@@ -870,7 +878,7 @@ func (c *containerMounter) mountSubmount(ctx context.Context, spec *specs.Spec,
// getMountNameAndOptions retrieves the fsName, opts, and useOverlay values
// used for mounts.
func getMountNameAndOptions(spec *specs.Spec, conf *config.Config, m *mountInfo, productName string) (string, *vfs.MountOptions, error) {
func getMountNameAndOptions(spec *specs.Spec, conf *config.Config, m *mountInfo, productName, containerName string) (string, *vfs.MountOptions, error) {
fsName := m.mount.Type
var (
mopts = m.mount.Options
@@ -906,7 +914,7 @@ func getMountNameAndOptions(spec *specs.Spec, conf *config.Config, m *mountInfo,
}
internalData = tmpfs.FilesystemOpts{
MemoryFile: mf,
UniqueID: m.mount.Destination,
UniqueID: vfs.RestoreID{ContainerName: containerName, Path: m.mount.Destination},
// If a mount is being overlaid with tmpfs, it should not be limited by
// the default tmpfs size limit.
DisableDefaultSizeLimit: true,
@@ -926,7 +934,10 @@ func getMountNameAndOptions(spec *specs.Spec, conf *config.Config, m *mountInfo,
}
data = append(data, goferMountData(m.goferFD.Release(), getMountAccessType(conf, m.hint), conf)...)
internalData = gofer.InternalFilesystemOptions{
UniqueID: m.mount.Destination,
UniqueID: vfs.RestoreID{
ContainerName: containerName,
Path: m.mount.Destination,
},
}
case cgroupfs.Name:
@@ -1204,7 +1215,7 @@ func (c *containerMounter) mountSharedMaster(ctx context.Context, spec *specs.Sp
// Mount the master using the options from the hint (mount annotations).
origOpts := mntInfo.mount.Options
mntInfo.mount.Options = mntInfo.hint.Mount.Options
fsName, opts, err := getMountNameAndOptions(spec, conf, mntInfo, c.productName)
fsName, opts, err := getMountNameAndOptions(spec, conf, mntInfo, c.productName, c.containerName)
mntInfo.mount.Options = origOpts
if err != nil {
return nil, err
@@ -1271,15 +1282,18 @@ func (c *containerMounter) makeMountPoint(ctx context.Context, creds *auth.Crede
func (c *containerMounter) configureRestore(ctx context.Context) (context.Context, error) {
// Compare createMountNamespace(); rootfs always consumes a gofer FD and a
// filestore FD is consumed if the rootfs GoferMountConf indicates so.
fdmap := make(map[string]int)
fdmap["/"] = c.goferFDs.remove()
mfmap := make(map[string]*pgalloc.MemoryFile)
fdmap := make(map[vfs.RestoreID]int)
rootKey := vfs.RestoreID{ContainerName: c.containerName, Path: "/"}
fdmap[rootKey] = c.goferFDs.remove()
mfmap := make(map[vfs.RestoreID]*pgalloc.MemoryFile)
if rootfsConf := c.goferMountConfs[0]; rootfsConf.IsFilestorePresent() {
mf, err := createPrivateMemoryFile(c.goferFilestoreFDs.removeAsFD().ReleaseToFile("overlay-filestore"))
if err != nil {
return ctx, fmt.Errorf("failed to create private memory file for mount rootfs: %w", err)
}
mfmap["/"] = mf
mfmap[rootKey] = mf
}
// prepareMounts() consumes the remaining FDs for submounts.
mounts, err := c.prepareMounts()
@@ -1289,14 +1303,16 @@ func (c *containerMounter) configureRestore(ctx context.Context) (context.Contex
for i := range mounts {
submount := &mounts[i]
if submount.goferFD != nil {
fdmap[submount.mount.Destination] = submount.goferFD.Release()
key := vfs.RestoreID{ContainerName: c.containerName, Path: submount.mount.Destination}
fdmap[key] = submount.goferFD.Release()
}
if submount.filestoreFD != nil {
mf, err := createPrivateMemoryFile(submount.filestoreFD.ReleaseToFile("overlay-filestore"))
if err != nil {
return ctx, fmt.Errorf("failed to create private memory file for mount %q: %w", submount.mount.Destination, err)
}
mfmap[submount.mount.Destination] = mf
key := vfs.RestoreID{ContainerName: c.containerName, Path: submount.mount.Destination}
mfmap[key] = mf
}
}
return context.WithValue(context.WithValue(ctx, vfs.CtxRestoreFilesystemFDMap, fdmap), vfs.CtxFilesystemMemoryFileMap, mfmap), nil
+7 -9
View File
@@ -241,15 +241,7 @@ func fixSpec(spec *specs.Spec, bundleDir string, conf *config.Config) error {
}
}
// Check annotation to see if container name is available.
var containerName string
for key, val := range spec.Annotations {
if key == annotationContainerName {
containerName = val
log.Debugf("Container name: %q", containerName)
break
}
}
containerName := ContainerName(spec)
for annotation, val := range spec.Annotations {
if strings.HasPrefix(annotation, annotationFlagPrefix) {
// Override flags using annotation to allow customization per sandbox
@@ -758,3 +750,9 @@ func ResolveEnvs(envs ...[]string) ([]string, error) {
func FaqErrorMsg(anchor, msg string) string {
return fmt.Sprintf("%s; see https://gvisor.dev/faq#%s for more details", msg, anchor)
}
// ContainerName looks for an annotation in the spec with the container name. Returns empty string
// if no annotation is found.
func ContainerName(spec *specs.Spec) string {
return spec.Annotations[annotationContainerName]
}