Files
podman/pkg/domain/infra/abi/system.go
Matthew Heon b53cb57680 Initial implementation of volume plugins
This implements support for mounting and unmounting volumes
backed by volume plugins. Support for actually retrieving
plugins requires a pull request to land in containers.conf and
then that to be vendored, and as such is not yet ready. Given
this, this code is only compile tested. However, the code for
everything past retrieving the plugin has been written - there is
support for creating, removing, mounting, and unmounting volumes,
which should allow full functionality once the c/common PR is
merged.

A major change is the signature of the MountPoint function for
volumes, which now, by necessity, returns an error. Named volumes
managed by a plugin do not have a mountpoint we control; instead,
it is managed entirely by the plugin. As such, we need to cache
the path in the DB, and calls to retrieve it now need to access
the DB (and may fail as such).

Notably absent is support for SELinux relabelling and chowning
these volumes. Given that we don't manage the mountpoint for
these volumes, I am extremely reluctant to try and modify it - we
could easily break the plugin trying to chown or relabel it.

Also, we had no less than *5* separate implementations of
inspecting a volume floating around in pkg/infra/abi and
pkg/api/handlers/libpod. And none of them used volume.Inspect(),
the only correct way of inspecting volumes. Remove them all and
consolidate to using the correct way. Compat API is likely still
doing things the wrong way, but that is an issue for another day.

Fixes #4304

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2021-01-14 15:35:33 -05:00

411 lines
12 KiB
Go

package abi
import (
"context"
"fmt"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/cgroups"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/entities/reports"
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/util"
"github.com/containers/podman/v2/utils"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
info, err := ic.Libpod.Info()
if err != nil {
return nil, err
}
xdg, err := util.GetRuntimeDir()
if err != nil {
return nil, err
}
if len(xdg) == 0 {
// If no xdg is returned, assume root socket
xdg = "/run"
}
// Glue the socket path together
socketPath := filepath.Join(xdg, "podman", "podman.sock")
rs := define.RemoteSocket{
Path: socketPath,
Exists: false,
}
// Check if the socket exists
if fi, err := os.Stat(socketPath); err == nil {
if fi.Mode()&os.ModeSocket != 0 {
rs.Exists = true
}
}
// TODO
// it was suggested future versions of this could perform
// a ping on the socket for greater confidence the socket is
// actually active.
info.Host.RemoteSocket = &rs
return info, err
}
func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error {
// do it only after podman has already re-execed and running with uid==0.
if os.Geteuid() == 0 {
ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup()
if err != nil {
logrus.Warnf("Failed to detect the owner for the current cgroup: %v", err)
}
if !ownsCgroup {
conf, err := ic.Config(context.Background())
if err != nil {
return err
}
initCommand, err := ioutil.ReadFile("/proc/1/comm")
// On errors, default to systemd
runsUnderSystemd := err != nil || strings.TrimRight(string(initCommand), "\n") == "systemd"
unitName := fmt.Sprintf("podman-%d.scope", os.Getpid())
if runsUnderSystemd || conf.Engine.CgroupManager == config.SystemdCgroupsManager {
if err := utils.RunUnderSystemdScope(os.Getpid(), "user.slice", unitName); err != nil {
logrus.Warnf("Failed to add podman to systemd sandbox cgroup: %v", err)
}
}
}
return nil
}
tmpDir, err := ic.Libpod.TmpDir()
if err != nil {
return err
}
pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
became, ret, err := rootless.TryJoinPauseProcess(pausePidPath)
if err != nil {
return err
}
if became {
os.Exit(ret)
}
// if there is no pid file, try to join existing containers, and create a pause process.
ctrs, err := ic.Libpod.GetRunningContainers()
if err != nil {
logrus.Error(err.Error())
os.Exit(1)
}
paths := []string{}
for _, ctr := range ctrs {
paths = append(paths, ctr.Config().ConmonPidFile)
}
became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths)
if err := movePauseProcessToScope(ic.Libpod); err != nil {
conf, err := ic.Config(context.Background())
if err != nil {
return err
}
if conf.Engine.CgroupManager == config.SystemdCgroupsManager {
logrus.Warnf("Failed to add pause process to systemd sandbox cgroup: %v", err)
} else {
logrus.Debugf("Failed to add pause process to systemd sandbox cgroup: %v", err)
}
}
if err != nil {
logrus.Error(errors.Wrapf(err, "invalid internal status, try resetting the pause process with %q", os.Args[0]+" system migrate"))
os.Exit(1)
}
if became {
os.Exit(ret)
}
return nil
}
func movePauseProcessToScope(r *libpod.Runtime) error {
tmpDir, err := r.TmpDir()
if err != nil {
return err
}
pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
data, err := ioutil.ReadFile(pausePidPath)
if err != nil {
return errors.Wrapf(err, "cannot read pause pid file")
}
pid, err := strconv.ParseUint(string(data), 10, 0)
if err != nil {
return errors.Wrapf(err, "cannot parse pid file %s", pausePidPath)
}
return utils.RunUnderSystemdScope(int(pid), "user.slice", "podman-pause.scope")
}
// SystemPrune removes unused data from the system. Pruning pods, containers, volumes and images.
func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
var systemPruneReport = new(entities.SystemPruneReport)
var filters []string
reclaimedSpace := (uint64)(0)
found := true
for found {
found = false
podPruneReport, err := ic.prunePodHelper(ctx)
if err != nil {
return nil, err
}
if len(podPruneReport) > 0 {
found = true
}
systemPruneReport.PodPruneReport = append(systemPruneReport.PodPruneReport, podPruneReport...)
// TODO: Figure out cleaner way to handle all of the different PruneOptions
containerPruneOptions := entities.ContainerPruneOptions{}
containerPruneOptions.Filters = (url.Values)(options.Filters)
containerPruneReports, err := ic.ContainerPrune(ctx, containerPruneOptions)
if err != nil {
return nil, err
}
reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(containerPruneReports)
systemPruneReport.ContainerPruneReports = append(systemPruneReport.ContainerPruneReports, containerPruneReports...)
for k, v := range options.Filters {
filters = append(filters, fmt.Sprintf("%s=%s", k, v[0]))
}
imagePruneReports, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, filters)
reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(imagePruneReports)
if err != nil {
return nil, err
}
if len(imagePruneReports) > 0 {
found = true
}
systemPruneReport.ImagePruneReports = append(systemPruneReport.ImagePruneReports, imagePruneReports...)
if options.Volume {
volumePruneOptions := entities.VolumePruneOptions{}
volumePruneOptions.Filters = (url.Values)(options.Filters)
volumePruneReport, err := ic.VolumePrune(ctx, volumePruneOptions)
if err != nil {
return nil, err
}
if len(volumePruneReport) > 0 {
found = true
}
reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(volumePruneReport)
systemPruneReport.VolumePruneReports = append(systemPruneReport.VolumePruneReports, volumePruneReport...)
}
}
systemPruneReport.ReclaimedSpace = reclaimedSpace
return systemPruneReport, nil
}
func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.SystemDfOptions) (*entities.SystemDfReport, error) {
var (
dfImages = []*entities.SystemDfImageReport{}
)
// Compute disk-usage stats for all local images.
imgs, err := ic.Libpod.ImageRuntime().GetImages()
if err != nil {
return nil, err
}
imageStats, err := ic.Libpod.ImageRuntime().DiskUsage(ctx, imgs)
if err != nil {
return nil, err
}
for _, stat := range imageStats {
report := entities.SystemDfImageReport{
Repository: stat.Repository,
Tag: stat.Tag,
ImageID: stat.ID,
Created: stat.Created,
Size: int64(stat.Size),
SharedSize: int64(stat.SharedSize),
UniqueSize: int64(stat.UniqueSize),
Containers: stat.Containers,
}
dfImages = append(dfImages, &report)
}
// Get Containers and iterate them
cons, err := ic.Libpod.GetAllContainers()
if err != nil {
return nil, err
}
dfContainers := make([]*entities.SystemDfContainerReport, 0, len(cons))
for _, c := range cons {
iid, _ := c.Image()
state, err := c.State()
if err != nil {
return nil, errors.Wrapf(err, "Failed to get state of container %s", c.ID())
}
conSize, err := c.RootFsSize()
if err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
logrus.Error(errors.Wrapf(err, "Failed to get root file system size of container %s", c.ID()))
} else {
return nil, errors.Wrapf(err, "Failed to get root file system size of container %s", c.ID())
}
}
rwsize, err := c.RWSize()
if err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
logrus.Error(errors.Wrapf(err, "Failed to get read/write size of container %s", c.ID()))
} else {
return nil, errors.Wrapf(err, "Failed to get read/write size of container %s", c.ID())
}
}
report := entities.SystemDfContainerReport{
ContainerID: c.ID(),
Image: iid,
Command: c.Command(),
LocalVolumes: len(c.UserVolumes()),
RWSize: rwsize,
Size: conSize,
Created: c.CreatedTime(),
Status: state.String(),
Names: c.Name(),
}
dfContainers = append(dfContainers, &report)
}
// Get volumes and iterate them
vols, err := ic.Libpod.GetAllVolumes()
if err != nil {
return nil, err
}
running, err := ic.Libpod.GetRunningContainers()
if err != nil {
return nil, err
}
runningContainers := make([]string, 0, len(running))
for _, c := range running {
runningContainers = append(runningContainers, c.ID())
}
dfVolumes := make([]*entities.SystemDfVolumeReport, 0, len(vols))
var reclaimableSize int64
for _, v := range vols {
var consInUse int
mountPoint, err := v.MountPoint()
if err != nil {
return nil, err
}
if mountPoint == "" {
// We can't get any info on this volume, as it's not
// mounted.
// TODO: fix this.
continue
}
volSize, err := sizeOfPath(mountPoint)
if err != nil {
return nil, err
}
inUse, err := v.VolumeInUse()
if err != nil {
return nil, err
}
if len(inUse) == 0 {
reclaimableSize += volSize
}
for _, viu := range inUse {
if util.StringInSlice(viu, runningContainers) {
consInUse++
}
}
report := entities.SystemDfVolumeReport{
VolumeName: v.Name(),
Links: consInUse,
Size: volSize,
ReclaimableSize: reclaimableSize,
}
dfVolumes = append(dfVolumes, &report)
}
return &entities.SystemDfReport{
Images: dfImages,
Containers: dfContainers,
Volumes: dfVolumes,
}, nil
}
// sizeOfPath determines the file usage of a given path. it was called volumeSize in v1
// and now is made to be generic and take a path instead of a libpod volume
func sizeOfPath(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
size += info.Size()
}
return err
})
return size, err
}
func (se *SystemEngine) Reset(ctx context.Context) error {
return se.Libpod.Reset(ctx)
}
func (se *SystemEngine) Renumber(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig) error {
return nil
}
func (se SystemEngine) Migrate(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig, options entities.SystemMigrateOptions) error {
return nil
}
func (se SystemEngine) Shutdown(ctx context.Context) {
if err := se.Libpod.Shutdown(false); err != nil {
logrus.Error(err)
}
}
func unshareEnv(graphroot, runroot string) []string {
return append(os.Environ(), "_CONTAINERS_USERNS_CONFIGURED=done",
fmt.Sprintf("CONTAINERS_GRAPHROOT=%s", graphroot),
fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot))
}
func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error {
cmd := exec.Command(args[0], args[1:]...)
cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) {
var report entities.SystemVersionReport
v, err := define.GetVersion()
if err != nil {
return nil, err
}
report.Client = &v
return &report, err
}