Clean machine pull cache

Cache cleanups only happen if there is a cache miss, and we need to pull a new image

For quay.io/podman/machine-os, we remove all old images from the cache dir. This means we will delete any file that exists in the cache dir; this should be safe to do since the machine pull code should be the only thing touching this cache dir. OCI machine images will always have a different manifest, and won’t be updated with the same manifest, so if the version moves on, there isn’t a reason to keep the old version in the cache, it really doesn’t change.

For Fedora (WSL), we use the cache, so we go through the cache dir and remove any old cached images, on a cache miss. We also switch to using ~/.local/share/containers/podman/machine/wsl/cache as the cache dir rather than ~/.local/share/containers/podman/machine/wsl. Both these behaviors existed in v4.9, but are now added back into 5.x.

For generic files pulled from a URL or a non-default OCI image, we shouldn’t actually cache, so we delete the pulled file immediately after creating a machine image. This restores the behavior from v4.9.

For generic files from a local path, the original file will never be cleaned up

Unsure how to test, so:
[NO NEW TESTS NEEDED]

Signed-off-by: Ashley Cui <acui@redhat.com>
This commit is contained in:
Ashley Cui
2024-04-25 09:32:57 -04:00
parent 273020160c
commit e412eff33f
5 changed files with 96 additions and 19 deletions

View File

@ -26,7 +26,7 @@ func pullOCITestDisk(finalDir string, vmType define.VMType) error {
if err != nil { if err != nil {
return err return err
} }
err = ociArtPull.GetNoCompress() _, err = ociArtPull.GetNoCompress()
if err != nil { if err != nil {
return err return err
} }

View File

@ -32,6 +32,7 @@ const (
) )
type OCIArtifactDisk struct { type OCIArtifactDisk struct {
cache bool
cachedCompressedDiskPath *define.VMFile cachedCompressedDiskPath *define.VMFile
name string name string
ctx context.Context ctx context.Context
@ -91,12 +92,15 @@ func NewOCIArtifactPull(ctx context.Context, dirs *define.MachineDirs, endpoint
os: machineOS, os: machineOS,
} }
cache := false
if endpoint == "" { if endpoint == "" {
endpoint = fmt.Sprintf("docker://%s/%s/%s:%s", artifactRegistry, artifactRepo, artifactImageName, artifactVersion.majorMinor()) endpoint = fmt.Sprintf("docker://%s/%s/%s:%s", artifactRegistry, artifactRepo, artifactImageName, artifactVersion.majorMinor())
cache = true
} }
ociDisk := OCIArtifactDisk{ ociDisk := OCIArtifactDisk{
ctx: ctx, ctx: ctx,
cache: cache,
dirs: dirs, dirs: dirs,
diskArtifactOpts: &diskOpts, diskArtifactOpts: &diskOpts,
finalPath: finalPath.GetPath(), finalPath: finalPath.GetPath(),
@ -114,36 +118,46 @@ func (o *OCIArtifactDisk) OriginalFileName() (string, string) {
} }
func (o *OCIArtifactDisk) Get() error { func (o *OCIArtifactDisk) Get() error {
if err := o.get(); err != nil { cleanCache, err := o.get()
if err != nil {
return err return err
} }
if cleanCache != nil {
defer cleanCache()
}
return o.decompress() return o.decompress()
} }
func (o *OCIArtifactDisk) GetNoCompress() error { func (o *OCIArtifactDisk) GetNoCompress() (func(), error) {
return o.get() return o.get()
} }
func (o *OCIArtifactDisk) get() error { func (o *OCIArtifactDisk) get() (func(), error) {
cleanCache := func() {}
destRef, artifactDigest, err := o.getDestArtifact() destRef, artifactDigest, err := o.getDestArtifact()
if err != nil { if err != nil {
return err return nil, err
} }
// Note: the artifactDigest here is the hash of the most recent disk image available // Note: the artifactDigest here is the hash of the most recent disk image available
cachedImagePath, err := o.dirs.ImageCacheDir.AppendToNewVMFile(fmt.Sprintf("%s.%s", artifactDigest.Encoded(), o.vmType.ImageFormat().KindWithCompression()), nil) cachedImagePath, err := o.dirs.ImageCacheDir.AppendToNewVMFile(fmt.Sprintf("%s.%s", artifactDigest.Encoded(), o.vmType.ImageFormat().KindWithCompression()), nil)
if err != nil { if err != nil {
return err return nil, err
} }
// check if we have the latest and greatest disk image // check if we have the latest and greatest disk image
if _, err = os.Stat(cachedImagePath.GetPath()); err != nil { if _, err = os.Stat(cachedImagePath.GetPath()); err != nil {
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("unable to access cached image path %q: %q", cachedImagePath.GetPath(), err) return nil, fmt.Errorf("unable to access cached image path %q: %q", cachedImagePath.GetPath(), err)
} }
// On cache misses, we clean out the cache
cleanCache = o.cleanCache(cachedImagePath.GetPath())
// pull the image down to our local filesystem // pull the image down to our local filesystem
if err := o.pull(destRef, artifactDigest); err != nil { if err := o.pull(destRef, artifactDigest); err != nil {
return fmt.Errorf("failed to pull %s: %w", destRef.DockerReference(), err) return nil, fmt.Errorf("failed to pull %s: %w", destRef.DockerReference(), err)
} }
// grab the artifact disk out of the cache and lay // grab the artifact disk out of the cache and lay
// it into our local cache in the format of // it into our local cache in the format of
@ -153,13 +167,46 @@ func (o *OCIArtifactDisk) get() error {
// //
// i.e. 91d1e51...d28974.qcow2.xz // i.e. 91d1e51...d28974.qcow2.xz
if err := o.unpack(artifactDigest); err != nil { if err := o.unpack(artifactDigest); err != nil {
return err return nil, err
} }
} else { } else {
logrus.Debugf("cached image exists and is latest: %s", cachedImagePath.GetPath()) logrus.Debugf("cached image exists and is latest: %s", cachedImagePath.GetPath())
o.cachedCompressedDiskPath = cachedImagePath o.cachedCompressedDiskPath = cachedImagePath
} }
return cleanCache, nil
}
func (o *OCIArtifactDisk) cleanCache(cachedImagePath string) func() {
// cache miss while using an image that we cache, ie the default image
// clean out all old files fron the cache dir
if o.cache {
files, err := os.ReadDir(o.dirs.ImageCacheDir.GetPath())
if err != nil {
logrus.Warn("failed to clean machine image cache: ", err)
return nil return nil
}
return func() {
for _, file := range files {
path := filepath.Join(o.dirs.ImageCacheDir.GetPath(), file.Name())
logrus.Debugf("cleaning cached file: %s", path)
err := utils.GuardedRemoveAll(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
logrus.Warn("failed to clean machine image cache: ", err)
}
}
}
} else {
// using an image that we don't cache, ie not the default image
// delete image after use and don't cache
return func() {
logrus.Debugf("cleaning cache: %s", o.dirs.ImageCacheDir.GetPath())
err := os.Remove(cachedImagePath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
logrus.Warn("failed to clean pulled machine image: ", err)
}
}
}
} }
func (o *OCIArtifactDisk) getDestArtifact() (types.ImageReference, digest.Digest, error) { func (o *OCIArtifactDisk) getDestArtifact() (types.ImageReference, digest.Digest, error) {

View File

@ -20,7 +20,7 @@ func GetDisk(userInputPath string, dirs *define.MachineDirs, imagePath *define.V
} else { } else {
if strings.HasPrefix(userInputPath, "http") { if strings.HasPrefix(userInputPath, "http") {
// TODO probably should use tempdir instead of datadir // TODO probably should use tempdir instead of datadir
mydisk, err = stdpull.NewDiskFromURL(userInputPath, imagePath, dirs.DataDir, nil) mydisk, err = stdpull.NewDiskFromURL(userInputPath, imagePath, dirs.DataDir, nil, false)
} else { } else {
mydisk, err = stdpull.NewStdDiskPull(userInputPath, imagePath) mydisk, err = stdpull.NewStdDiskPull(userInputPath, imagePath)
} }

View File

@ -22,9 +22,10 @@ type DiskFromURL struct {
u *url2.URL u *url2.URL
finalPath *define.VMFile finalPath *define.VMFile
tempLocation *define.VMFile tempLocation *define.VMFile
cache bool
} }
func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.VMFile, optionalTempFileName *string) (*DiskFromURL, error) { func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.VMFile, optionalTempFileName *string, cache bool) (*DiskFromURL, error) {
var ( var (
err error err error
) )
@ -57,6 +58,7 @@ func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.
u: u, u: u,
finalPath: finalPath, finalPath: finalPath,
tempLocation: tempLocation, tempLocation: tempLocation,
cache: cache,
}, nil }, nil
} }
@ -65,6 +67,16 @@ func (d *DiskFromURL) Get() error {
if err := d.pull(); err != nil { if err := d.pull(); err != nil {
return err return err
} }
if !d.cache {
defer func() {
if err := utils.GuardedRemoveAll(d.tempLocation.GetPath()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
logrus.Warn("failed to clean machine image cache: ", err)
}
}
}()
}
logrus.Debugf("decompressing (if needed) %s to %s", d.tempLocation.GetPath(), d.finalPath.GetPath()) logrus.Debugf("decompressing (if needed) %s to %s", d.tempLocation.GetPath(), d.finalPath.GetPath())
return compression.Decompress(d.tempLocation, d.finalPath.GetPath()) return compression.Decompress(d.tempLocation, d.finalPath.GetPath())
} }

View File

@ -15,6 +15,7 @@ import (
"github.com/containers/podman/v5/pkg/machine/shim/diskpull" "github.com/containers/podman/v5/pkg/machine/shim/diskpull"
"github.com/containers/podman/v5/pkg/machine/stdpull" "github.com/containers/podman/v5/pkg/machine/stdpull"
"github.com/containers/podman/v5/pkg/machine/wsl/wutil" "github.com/containers/podman/v5/pkg/machine/wsl/wutil"
"github.com/containers/podman/v5/utils"
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine"
@ -303,8 +304,7 @@ func (w WSLStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *
// i.e.v39.0.31-rootfs.tar.xz // i.e.v39.0.31-rootfs.tar.xz
versionedBase := fmt.Sprintf("%s-%s", downloadVersion, filepath.Base(downloadURL.Path)) versionedBase := fmt.Sprintf("%s-%s", downloadVersion, filepath.Base(downloadURL.Path))
// TODO we need a mechanism for "flushing" old cache files cachedFile, err := dirs.ImageCacheDir.AppendToNewVMFile(versionedBase, nil)
cachedFile, err := dirs.DataDir.AppendToNewVMFile(versionedBase, nil)
if err != nil { if err != nil {
return err return err
} }
@ -313,13 +313,31 @@ func (w WSLStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *
if _, err = os.Stat(cachedFile.GetPath()); err == nil { if _, err = os.Stat(cachedFile.GetPath()); err == nil {
logrus.Debugf("%q already exists locally", cachedFile.GetPath()) logrus.Debugf("%q already exists locally", cachedFile.GetPath())
myDisk, err = stdpull.NewStdDiskPull(cachedFile.GetPath(), mc.ImagePath) myDisk, err = stdpull.NewStdDiskPull(cachedFile.GetPath(), mc.ImagePath)
} else {
// no cached file
myDisk, err = stdpull.NewDiskFromURL(downloadURL.String(), mc.ImagePath, dirs.DataDir, &versionedBase)
}
if err != nil { if err != nil {
return err return err
} }
} else {
files, err := os.ReadDir(dirs.ImageCacheDir.GetPath())
if err != nil {
logrus.Warn("failed to clean machine image cache: ", err)
} else {
defer func() {
for _, file := range files {
path := filepath.Join(dirs.ImageCacheDir.GetPath(), file.Name())
logrus.Debugf("cleaning cached image: %s", path)
err := utils.GuardedRemoveAll(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
logrus.Warn("failed to clean machine image cache: ", err)
}
}
}()
}
myDisk, err = stdpull.NewDiskFromURL(downloadURL.String(), mc.ImagePath, dirs.ImageCacheDir, &versionedBase, true)
if err != nil {
return err
}
}
// up until now, nothing has really happened // up until now, nothing has really happened
// pull if needed and decompress to image location // pull if needed and decompress to image location
return myDisk.Get() return myDisk.Get()