Files
Jan Rodák cfe4d46d89 Optimize image loading for Podman machines
Add support for loading images directly from machine paths to avoid
unnecessary file transfers when the image archive is already accessible
on the running machine through mounted directories.

Changes include:
- New /libpod/local/images/load API endpoint for direct machine loading
- Machine detection and path mapping functionality
- Fallback in tunnel mode to try optimized loading first

This optimization significantly speeds up image loading operations
when working with remote Podman machines by eliminating redundant
file transfers for already-accessible image archives.

Fixes: https://issues.redhat.com/browse/RUN-3249
Fixes: https://github.com/containers/podman/issues/26321

Signed-off-by: Jan Rodák <hony.com@seznam.cz>
2025-08-19 12:33:15 +02:00

157 lines
5.0 KiB
Go

//go:build amd64 || arm64
package localapi
import (
"context"
"errors"
"fmt"
"io/fs"
"net/url"
"path/filepath"
"strconv"
"strings"
"github.com/containers/podman/v5/pkg/bindings"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/env"
"github.com/containers/podman/v5/pkg/machine/provider"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/containers/podman/v5/pkg/specgen"
"github.com/containers/storage/pkg/fileutils"
"github.com/sirupsen/logrus"
)
// FindMachineByPort finds a running machine that matches the given connection port.
// It returns the machine configuration and provider, or an error if not found.
func FindMachineByPort(connectionURI string, parsedConnection *url.URL) (*vmconfigs.MachineConfig, vmconfigs.VMProvider, error) {
machineProvider, err := provider.Get()
if err != nil {
return nil, nil, fmt.Errorf("getting machine provider: %w", err)
}
dirs, err := env.GetMachineDirs(machineProvider.VMType())
if err != nil {
return nil, nil, err
}
machineList, err := vmconfigs.LoadMachinesInDir(dirs)
if err != nil {
return nil, nil, fmt.Errorf("listing machines: %w", err)
}
// Now we know that the connection points to a machine and we
// can find the machine by looking for the one with the
// matching port.
connectionPort, err := strconv.Atoi(parsedConnection.Port())
if err != nil {
return nil, nil, fmt.Errorf("parsing connection port: %w", err)
}
for _, mc := range machineList {
if connectionPort != mc.SSH.Port {
continue
}
state, err := machineProvider.State(mc, false)
if err != nil {
return nil, nil, err
}
if state != define.Running {
return nil, nil, fmt.Errorf("machine %s is not running but in state %s", mc.Name, state)
}
return mc, machineProvider, nil
}
return nil, nil, fmt.Errorf("could not find a matching machine for connection %q", connectionURI)
}
// getMachineMountsAndVMType retrieves the mounts and VM type of a machine based on the connection URI and parsed URL.
// It returns a slice of mounts, the VM type, or an error if the machine cannot be found or is not running.
func getMachineMountsAndVMType(connectionURI string, parsedConnection *url.URL) ([]*vmconfigs.Mount, define.VMType, error) {
mc, machineProvider, err := FindMachineByPort(connectionURI, parsedConnection)
if err != nil {
return nil, define.UnknownVirt, err
}
return mc.Mounts, machineProvider.VMType(), nil
}
// isPathAvailableOnMachine checks if a local path is available on the machine through mounted directories.
// If the path is available, it returns a LocalAPIMap with the corresponding remote path.
func isPathAvailableOnMachine(mounts []*vmconfigs.Mount, vmType define.VMType, path string) (*LocalAPIMap, bool) {
pathABS, err := filepath.Abs(path)
if err != nil {
logrus.Debugf("Failed to get absolute path for %s: %v", path, err)
return nil, false
}
// WSLVirt is a special case where there is no real concept of doing a mount in WSL,
// WSL by default mounts the drives to /mnt/c, /mnt/d, etc...
if vmType == define.WSLVirt {
converted_path, err := specgen.ConvertWinMountPath(pathABS)
if err != nil {
logrus.Debugf("Failed to convert Windows mount path: %v", err)
return nil, false
}
return &LocalAPIMap{
ClientPath: pathABS,
RemotePath: converted_path,
}, true
}
for _, mount := range mounts {
mountSource := filepath.Clean(mount.Source)
relPath, err := filepath.Rel(mountSource, pathABS)
if err != nil {
logrus.Debugf("Failed to get relative path: %v", err)
continue
}
// If relPath starts with ".." or is absolute, pathABS is not under mountSource
if relPath == "." || (!strings.HasPrefix(relPath, "..") && !filepath.IsAbs(relPath)) {
target := filepath.Join(mount.Target, relPath)
converted_path, err := specgen.ConvertWinMountPath(target)
if err != nil {
logrus.Debugf("Failed to convert Windows mount path: %v", err)
return nil, false
}
logrus.Debugf("Converted client path: %q", converted_path)
return &LocalAPIMap{
ClientPath: pathABS,
RemotePath: converted_path,
}, true
}
}
return nil, false
}
// CheckPathOnRunningMachine is a convenience function that checks if a path is available
// on any currently running machine. It combines machine inspection and path checking.
func CheckPathOnRunningMachine(ctx context.Context, path string) (*LocalAPIMap, bool) {
if err := fileutils.Exists(path); errors.Is(err, fs.ErrNotExist) {
logrus.Debugf("Path %s does not exist locally, skipping machine check", path)
return nil, false
}
if machineMode := bindings.GetMachineMode(ctx); !machineMode {
logrus.Debug("Machine mode is not enabled, skipping machine check")
return nil, false
}
conn, err := bindings.GetClient(ctx)
if err != nil {
logrus.Debugf("Failed to get client connection: %v", err)
return nil, false
}
mounts, vmType, err := getMachineMountsAndVMType(conn.URI.String(), conn.URI)
if err != nil {
logrus.Debugf("Failed to get machine mounts: %v", err)
return nil, false
}
return isPathAvailableOnMachine(mounts, vmType, path)
}