Add support for containers.conf volume timeouts

Also, do a general cleanup of all the timeout code. Changes
include:
- Convert from int to *uint where possible. Timeouts cannot be
  negative, hence the uint change; and a timeout of 0 is valid,
  so we need a new way to detect that the user set a timeout
  (hence, pointer).
- Change name in the database to avoid conflicts between new data
  type and old one. This will cause timeouts set with 4.2.0 to be
  lost, but considering nobody is using the feature at present
  (and the lack of validation means we could have invalid,
  negative timeouts in the DB) this feels safe.
- Ensure volume plugin timeouts can only be used with volumes
  created using a plugin. Timeouts on the local driver are
  nonsensical.
- Remove the existing test, as it did not use a volume plugin.
  Write a new test that does.

The actual plumbing of the containers.conf timeout in is one line
in volume_api.go; the remainder are the above-described cleanups.

Signed-off-by: Matthew Heon <mheon@redhat.com>
This commit is contained in:
Matthew Heon
2022-08-23 14:46:43 -04:00
parent 3bcd8047cf
commit 0f73935563
36 changed files with 286 additions and 110 deletions

View File

@ -190,7 +190,7 @@ func (i *Image) Inspect(ctx context.Context, options *InspectOptions) (*ImageDat
// NOTE: Health checks may be listed in the container config or
// the config.
data.HealthCheck = dockerManifest.ContainerConfig.Healthcheck
if data.HealthCheck == nil {
if data.HealthCheck == nil && dockerManifest.Config != nil {
data.HealthCheck = dockerManifest.Config.Healthcheck
}
}

View File

@ -99,7 +99,7 @@ func (r *Runtime) Load(ctx context.Context, path string, options *LoadOptions) (
}
// loadMultiImageDockerArchive loads the docker archive specified by ref. In
// case the path@reference notation was used, only the specifiec image will be
// case the path@reference notation was used, only the specified image will be
// loaded. Otherwise, all images will be loaded.
func (r *Runtime) loadMultiImageDockerArchive(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) {
// If we cannot stat the path, it either does not exist OR the correct

View File

@ -19,6 +19,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/storage/pkg/lockfile"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
type cniNetwork struct {
@ -62,6 +63,8 @@ type InitConfig struct {
CNIConfigDir string
// CNIPluginDirs is a list of directories where cni should look for the plugins.
CNIPluginDirs []string
// RunDir is a directory where temporary files can be stored.
RunDir string
// DefaultNetwork is the name for the default network.
DefaultNetwork string
@ -81,7 +84,16 @@ func NewCNINetworkInterface(conf *InitConfig) (types.ContainerNetwork, error) {
// TODO: consider using a shared memory lock
lock, err := lockfile.GetLockfile(filepath.Join(conf.CNIConfigDir, "cni.lock"))
if err != nil {
return nil, err
// If we're on a read-only filesystem, there is no risk of
// contention. Fall back to a local lockfile.
if errors.Is(err, unix.EROFS) {
lock, err = lockfile.GetLockfile(filepath.Join(conf.RunDir, "cni.lock"))
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
defaultNetworkName := conf.DefaultNetwork

View File

@ -169,6 +169,7 @@ func getCniInterface(conf *config.Config) (types.ContainerNetwork, error) {
return cni.NewCNINetworkInterface(&cni.InitConfig{
CNIConfigDir: confDir,
CNIPluginDirs: conf.Network.CNIPluginDirs,
RunDir: conf.Engine.TmpDir,
DefaultNetwork: conf.Network.DefaultNetwork,
DefaultSubnet: conf.Network.DefaultSubnet,
DefaultsubnetPools: conf.Network.DefaultSubnetPools,

View File

@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
@ -27,6 +28,8 @@ const (
_configPath = "containers/containers.conf"
// UserOverrideContainersConfig holds the containers config path overridden by the rootless user
UserOverrideContainersConfig = ".config/" + _configPath
// Token prefix for looking for helper binary under $BINDIR
bindirPrefix = "$BINDIR"
)
// RuntimeStateStore is a constant indicating which state store implementation
@ -454,6 +457,13 @@ type EngineConfig struct {
// may not be by other drivers.
VolumePath string `toml:"volume_path,omitempty"`
// VolumePluginTimeout sets the default timeout, in seconds, for
// operations that must contact a volume plugin. Plugins are external
// programs accessed via REST API; this sets a timeout for requests to
// that API.
// A value of 0 is treated as no timeout.
VolumePluginTimeout uint `toml:"volume_plugin_timeout,omitempty,omitzero"`
// VolumePlugins is a set of plugins that can be used as the backend for
// Podman named volumes. Each volume is specified as a name (what Podman
// will refer to the plugin as) mapped to a path, which must point to a
@ -815,6 +825,18 @@ func (c *Config) Validate() error {
return nil
}
// URI returns the URI Path to the machine image
func (m *MachineConfig) URI() string {
uri := m.Image
for _, val := range []string{"$ARCH", "$arch"} {
uri = strings.Replace(uri, val, runtime.GOARCH, 1)
}
for _, val := range []string{"$OS", "$os"} {
uri = strings.Replace(uri, val, runtime.GOOS, 1)
}
return uri
}
func (c *EngineConfig) findRuntime() string {
// Search for crun first followed by runc, kata, runsc
for _, name := range []string{"crun", "runc", "runj", "kata", "runsc"} {
@ -1241,10 +1263,37 @@ func (c *Config) ActiveDestination() (uri, identity string, err error) {
return "", "", errors.New("no service destination configured")
}
var (
bindirFailed = false
bindirCached = ""
)
func findBindir() string {
if bindirCached != "" || bindirFailed {
return bindirCached
}
execPath, err := os.Executable()
if err == nil {
// Resolve symbolic links to find the actual binary file path.
execPath, err = filepath.EvalSymlinks(execPath)
}
if err != nil {
// If failed to find executable (unlikely to happen), warn about it.
// The bindirFailed flag will track this, so we only warn once.
logrus.Warnf("Failed to find $BINDIR: %v", err)
bindirFailed = true
return ""
}
bindirCached = filepath.Dir(execPath)
return bindirCached
}
// FindHelperBinary will search the given binary name in the configured directories.
// If searchPATH is set to true it will also search in $PATH.
func (c *Config) FindHelperBinary(name string, searchPATH bool) (string, error) {
dirList := c.Engine.HelperBinariesDir
bindirPath := ""
bindirSearched := false
// If set, search this directory first. This is used in testing.
if dir, found := os.LookupEnv("CONTAINERS_HELPER_BINARY_DIR"); found {
@ -1252,6 +1301,24 @@ func (c *Config) FindHelperBinary(name string, searchPATH bool) (string, error)
}
for _, path := range dirList {
if path == bindirPrefix || strings.HasPrefix(path, bindirPrefix+string(filepath.Separator)) {
// Calculate the path to the executable first time we encounter a $BINDIR prefix.
if !bindirSearched {
bindirSearched = true
bindirPath = findBindir()
}
// If there's an error, don't stop the search for the helper binary.
// findBindir() will have warned once during the first failure.
if bindirPath == "" {
continue
}
// Replace the $BINDIR prefix with the path to the directory of the current binary.
if path == bindirPrefix {
path = bindirPath
} else {
path = filepath.Join(bindirPath, strings.TrimPrefix(path, bindirPrefix+string(filepath.Separator)))
}
}
fullpath := filepath.Join(path, name)
if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() {
return fullpath, nil

View File

@ -35,4 +35,6 @@ var defaultHelperBinariesDir = []string{
"/usr/local/lib/podman",
"/usr/libexec/podman",
"/usr/lib/podman",
// Relative to the binary directory
"$BINDIR/../libexec/podman",
}

View File

@ -605,6 +605,12 @@ default_sysctls = [
#
#volume_path = "/var/lib/containers/storage/volumes"
# Default timeout (in seconds) for volume plugin operations.
# Plugins are external programs accessed via a REST API; this sets a timeout
# for requests to that API.
# A value of 0 is treated as no timeout.
#volume_plugin_timeout = 5
# Paths to look for a valid OCI runtime (crun, runc, kata, runsc, krun, etc)
[engine.runtimes]
#crun = [
@ -665,9 +671,16 @@ default_sysctls = [
#
#disk_size=10
# The image used when creating a podman-machine VM.
# Default image URI when creating a new VM using `podman machine init`.
# Options: On Linux/Mac, `testing`, `stable`, `next`. On Windows, the major
# version of the OS (e.g `36`) for Fedora 36. For all platforms you can
# alternatively specify a custom download URL to an image. Container engines
# translate URIs $OS and $ARCH to the native OS and ARCH. URI
# "https://example.com/$OS/$ARCH/foobar.ami" becomes
# "https://example.com/linux/amd64/foobar.ami" on a Linux AMD machine.
# The default value is `testing`.
#
#image = "testing"
# image = "testing"
# Memory in MB a machine is created with.
#

View File

@ -168,6 +168,8 @@ const (
SeccompOverridePath = _etcDir + "/containers/seccomp.json"
// SeccompDefaultPath defines the default seccomp path.
SeccompDefaultPath = _installPrefix + "/share/containers/seccomp.json"
// DefaultVolumePluginTimeout is the default volume plugin timeout, in seconds
DefaultVolumePluginTimeout = 5
)
// DefaultConfig defines the default values from containers.conf.
@ -264,7 +266,7 @@ func defaultMachineConfig() MachineConfig {
Image: getDefaultMachineImage(),
Memory: 2048,
User: getDefaultMachineUser(),
Volumes: []string{"$HOME:$HOME"},
Volumes: getDefaultMachineVolumes(),
}
}
@ -304,6 +306,8 @@ func defaultConfigFromMemory() (*EngineConfig, error) {
c.StaticDir = filepath.Join(storeOpts.GraphRoot, "libpod")
c.VolumePath = filepath.Join(storeOpts.GraphRoot, "volumes")
c.VolumePluginTimeout = DefaultVolumePluginTimeout
c.HelperBinariesDir = defaultHelperBinariesDir
if additionalHelperBinariesDir != "" {
c.HelperBinariesDir = append(c.HelperBinariesDir, additionalHelperBinariesDir)

View File

@ -11,3 +11,8 @@ func getDefaultLockType() string {
func getLibpodTmpDir() string {
return "/run/libpod"
}
// getDefaultMachineVolumes returns default mounted volumes (possibly with env vars, which will be expanded)
func getDefaultMachineVolumes() []string {
return []string{"$HOME:$HOME"}
}

View File

@ -18,3 +18,8 @@ func getDefaultLockType() string {
func getLibpodTmpDir() string {
return "/var/run/libpod"
}
// getDefaultMachineVolumes returns default mounted volumes (possibly with env vars, which will be expanded)
func getDefaultMachineVolumes() []string {
return []string{"$HOME:$HOME"}
}

View File

@ -70,3 +70,8 @@ func getDefaultLockType() string {
func getLibpodTmpDir() string {
return "/run/libpod"
}
// getDefaultMachineVolumes returns default mounted volumes (possibly with env vars, which will be expanded)
func getDefaultMachineVolumes() []string {
return []string{"$HOME:$HOME"}
}

View File

@ -44,3 +44,8 @@ func getDefaultLockType() string {
func getLibpodTmpDir() string {
return "/run/libpod"
}
// getDefaultMachineVolumes returns default mounted volumes (possibly with env vars, which will be expanded)
func getDefaultMachineVolumes() []string {
return []string{}
}

View File

@ -372,7 +372,7 @@ func mountExists(mounts []rspec.Mount, dest string) bool {
return false
}
// resolveSymbolicLink resolves a possbile symlink path. If the path is a symlink, returns resolved
// resolveSymbolicLink resolves symlink paths. If the path is a symlink, returns resolved
// path; if not, returns the original path.
func resolveSymbolicLink(path string) (string, error) {
info, err := os.Lstat(path)