automation-tests/cmd/podman/system_df.go

651 lines
21 KiB
Go

//+build !remoteclient
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
dfSystemCommand cliconfig.SystemDfValues
dfSystemDescription = `
podman system df
Show podman disk usage
`
_dfSystemCommand = &cobra.Command{
Use: "df",
Args: noSubArgs,
Short: "Show podman disk usage",
Long: dfSystemDescription,
RunE: func(cmd *cobra.Command, args []string) error {
dfSystemCommand.GlobalFlags = MainGlobalOpts
dfSystemCommand.Remote = remoteclient
return dfSystemCmd(&dfSystemCommand)
},
}
)
type dfMetaData struct {
images []*image.Image
containers []*libpod.Container
activeContainers map[string]*libpod.Container
imagesUsedbyCtrMap map[string][]*libpod.Container
imagesUsedbyActiveCtr map[string][]*libpod.Container
volumes []*libpod.Volume
volumeUsedByContainerMap map[string][]*libpod.Container
}
type systemDfDiskUsage struct {
Type string
Total int
Active int
Size string
Reclaimable string
}
type imageVerboseDiskUsage struct {
Repository string
Tag string
ImageID string
Created string
Size string
SharedSize string
UniqueSize string
Containers int
}
type containerVerboseDiskUsage struct {
ContainerID string
Image string
Command string
LocalVolumes int
Size string
Created string
Status string
Names string
}
type volumeVerboseDiskUsage struct {
VolumeName string
Links int
Size string
}
const systemDfDefaultFormat string = "table {{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
const imageVerboseFormat string = "table {{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
const containerVerboseFormat string = "table {{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.Created}}\t{{.Status}}\t{{.Names}}"
const volumeVerboseFormat string = "table {{.VolumeName}}\t{{.Links}}\t{{.Size}}"
func init() {
dfSystemCommand.Command = _dfSystemCommand
dfSystemCommand.SetUsageTemplate(UsageTemplate())
flags := dfSystemCommand.Flags()
flags.BoolVarP(&dfSystemCommand.Verbose, "verbose", "v", false, "Show detailed information on space usage")
flags.StringVar(&dfSystemCommand.Format, "format", "", "Pretty-print images using a Go template")
}
func dfSystemCmd(c *cliconfig.SystemDfValues) error {
runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "Could not get runtime")
}
defer runtime.DeferredShutdown(false)
ctx := getContext()
metaData, err := getDfMetaData(ctx, runtime)
if err != nil {
return errors.Wrapf(err, "error getting disk usage data")
}
if c.Verbose {
err := verboseOutput(ctx, metaData)
if err != nil {
return err
}
return nil
}
systemDfDiskUsages, err := getDiskUsage(ctx, runtime, metaData)
if err != nil {
return errors.Wrapf(err, "error getting output of system df")
}
format := systemDfDefaultFormat
if c.Format != "" {
format = strings.Replace(c.Format, `\t`, "\t", -1)
}
return generateSysDfOutput(systemDfDiskUsages, format)
}
func generateSysDfOutput(systemDfDiskUsages []systemDfDiskUsage, format string) error {
var systemDfHeader = map[string]string{
"Type": "TYPE",
"Total": "TOTAL",
"Active": "ACTIVE",
"Size": "SIZE",
"Reclaimable": "RECLAIMABLE",
}
out := formats.StdoutTemplateArray{Output: systemDfDiskUsageToGeneric(systemDfDiskUsages), Template: format, Fields: systemDfHeader}
return out.Out()
}
func getDiskUsage(ctx context.Context, runtime *libpod.Runtime, metaData dfMetaData) ([]systemDfDiskUsage, error) {
imageDiskUsage, err := getImageDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap, metaData.imagesUsedbyActiveCtr)
if err != nil {
return nil, errors.Wrapf(err, "error getting disk usage of images")
}
containerDiskUsage, err := getContainerDiskUsage(metaData.containers, metaData.activeContainers)
if err != nil {
return nil, errors.Wrapf(err, "error getting disk usage of containers")
}
volumeDiskUsage, err := getVolumeDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap)
if err != nil {
return nil, errors.Wrapf(err, "error getting disk usage of volumess")
}
systemDfDiskUsages := []systemDfDiskUsage{imageDiskUsage, containerDiskUsage, volumeDiskUsage}
return systemDfDiskUsages, nil
}
func getDfMetaData(ctx context.Context, runtime *libpod.Runtime) (dfMetaData, error) {
var metaData dfMetaData
images, err := runtime.ImageRuntime().GetImages()
if err != nil {
return metaData, errors.Wrapf(err, "unable to get images")
}
containers, err := runtime.GetAllContainers()
if err != nil {
return metaData, errors.Wrapf(err, "error getting all containers")
}
volumes, err := runtime.GetAllVolumes()
if err != nil {
return metaData, errors.Wrap(err, "error getting all volumes")
}
activeContainers, err := activeContainers(containers)
if err != nil {
return metaData, errors.Wrapf(err, "error getting active containers")
}
imagesUsedbyCtrMap, imagesUsedbyActiveCtr, err := imagesUsedbyCtr(containers, activeContainers)
if err != nil {
return metaData, errors.Wrapf(err, "error getting getting images used by containers")
}
metaData = dfMetaData{
images: images,
containers: containers,
activeContainers: activeContainers,
imagesUsedbyCtrMap: imagesUsedbyCtrMap,
imagesUsedbyActiveCtr: imagesUsedbyActiveCtr,
volumes: volumes,
volumeUsedByContainerMap: volumeUsedByContainer(containers),
}
return metaData, nil
}
func imageUniqueSize(ctx context.Context, images []*image.Image) (map[string]uint64, error) {
imgUniqueSizeMap := make(map[string]uint64)
for _, img := range images {
parentImg := img
for {
next, err := parentImg.GetParent(ctx)
if err != nil {
return nil, errors.Wrapf(err, "error getting parent of image %s", parentImg.ID())
}
if next == nil {
break
}
parentImg = next
}
imgSize, err := img.Size(ctx)
if err != nil {
return nil, err
}
if img.ID() == parentImg.ID() {
imgUniqueSizeMap[img.ID()] = *imgSize
} else {
parentImgSize, err := parentImg.Size(ctx)
if err != nil {
return nil, errors.Wrapf(err, "error getting size of parent image %s", parentImg.ID())
}
imgUniqueSizeMap[img.ID()] = *imgSize - *parentImgSize
}
}
return imgUniqueSizeMap, nil
}
func getImageDiskUsage(ctx context.Context, images []*image.Image, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) {
var (
numberOfImages int
sumSize uint64
numberOfActiveImages int
unreclaimableSize uint64
imageDiskUsage systemDfDiskUsage
reclaimableStr string
)
imgUniqueSizeMap, err := imageUniqueSize(ctx, images)
if err != nil {
return imageDiskUsage, errors.Wrapf(err, "error getting unique size of images")
}
for _, img := range images {
unreclaimableSize += imageUsedSize(img, imgUniqueSizeMap, imageUsedbyCintainerMap, imageUsedbyActiveContainerMap)
isParent, err := img.IsParent(ctx)
if err != nil {
return imageDiskUsage, err
}
parent, err := img.GetParent(ctx)
if err != nil {
return imageDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID())
}
if isParent && parent != nil {
continue
}
numberOfImages++
if _, isActive := imageUsedbyCintainerMap[img.ID()]; isActive {
numberOfActiveImages++
}
if !isParent {
size, err := img.Size(ctx)
if err != nil {
return imageDiskUsage, errors.Wrapf(err, "error getting disk usage of image %s", img.ID())
}
sumSize += *size
}
}
sumSizeStr := units.HumanSizeWithPrecision(float64(sumSize), 3)
reclaimable := sumSize - unreclaimableSize
if sumSize != 0 {
reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize)
} else {
reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0)
}
imageDiskUsage = systemDfDiskUsage{
Type: "Images",
Total: numberOfImages,
Active: numberOfActiveImages,
Size: sumSizeStr,
Reclaimable: reclaimableStr,
}
return imageDiskUsage, nil
}
func imageUsedSize(img *image.Image, imgUniqueSizeMap map[string]uint64, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) uint64 {
var usedSize uint64
imgUnique := imgUniqueSizeMap[img.ID()]
if _, isCtrActive := imageUsedbyActiveContainerMap[img.ID()]; isCtrActive {
return imgUnique
}
containers := imageUsedbyCintainerMap[img.ID()]
for _, ctr := range containers {
if len(ctr.UserVolumes()) > 0 {
usedSize += imgUnique
return usedSize
}
}
return usedSize
}
func imagesUsedbyCtr(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (map[string][]*libpod.Container, map[string][]*libpod.Container, error) {
imgCtrMap := make(map[string][]*libpod.Container)
imgActiveCtrMap := make(map[string][]*libpod.Container)
for _, ctr := range containers {
imgID, _ := ctr.Image()
imgCtrMap[imgID] = append(imgCtrMap[imgID], ctr)
if _, isActive := activeContainers[ctr.ID()]; isActive {
imgActiveCtrMap[imgID] = append(imgActiveCtrMap[imgID], ctr)
}
}
return imgCtrMap, imgActiveCtrMap, nil
}
func getContainerDiskUsage(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (systemDfDiskUsage, error) {
var (
sumSize int64
unreclaimableSize int64
reclaimableStr string
)
for _, ctr := range containers {
size, err := ctr.RWSize()
if err != nil {
return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of container %s", ctr.ID())
}
sumSize += size
}
for _, activeCtr := range activeContainers {
size, err := activeCtr.RWSize()
if err != nil {
return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of active container %s", activeCtr.ID())
}
unreclaimableSize += size
}
if sumSize == 0 {
reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(0, 3), 0)
} else {
reclaimable := sumSize - unreclaimableSize
reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize)
}
containerDiskUsage := systemDfDiskUsage{
Type: "Containers",
Total: len(containers),
Active: len(activeContainers),
Size: units.HumanSizeWithPrecision(float64(sumSize), 3),
Reclaimable: reclaimableStr,
}
return containerDiskUsage, nil
}
func ctrIsActive(ctr *libpod.Container) (bool, error) {
state, err := ctr.State()
if err != nil {
return false, err
}
return state == define.ContainerStatePaused || state == define.ContainerStateRunning, nil
}
func activeContainers(containers []*libpod.Container) (map[string]*libpod.Container, error) {
activeContainers := make(map[string]*libpod.Container)
for _, aCtr := range containers {
isActive, err := ctrIsActive(aCtr)
if err != nil {
return nil, err
}
if isActive {
activeContainers[aCtr.ID()] = aCtr
}
}
return activeContainers, nil
}
func getVolumeDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) {
var (
sumSize int64
unreclaimableSize int64
reclaimableStr string
)
for _, volume := range volumes {
size, err := volumeSize(volume)
if err != nil {
return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of volime %s", volume.Name())
}
sumSize += size
if _, exist := volumeUsedByContainerMap[volume.Name()]; exist {
unreclaimableSize += size
}
}
reclaimable := sumSize - unreclaimableSize
if sumSize != 0 {
reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize)
} else {
reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0)
}
volumesDiskUsage := systemDfDiskUsage{
Type: "Local Volumes",
Total: len(volumes),
Active: len(volumeUsedByContainerMap),
Size: units.HumanSizeWithPrecision(float64(sumSize), 3),
Reclaimable: reclaimableStr,
}
return volumesDiskUsage, nil
}
func volumeUsedByContainer(containers []*libpod.Container) map[string][]*libpod.Container {
volumeUsedByContainerMap := make(map[string][]*libpod.Container)
for _, ctr := range containers {
ctrVolumes := ctr.UserVolumes()
for _, ctrVolume := range ctrVolumes {
volumeUsedByContainerMap[ctrVolume] = append(volumeUsedByContainerMap[ctrVolume], ctr)
}
}
return volumeUsedByContainerMap
}
func volumeSize(volume *libpod.Volume) (int64, error) {
var size int64
err := filepath.Walk(volume.MountPoint(), func(path string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
size += info.Size()
}
return err
})
return size, err
}
func getImageVerboseDiskUsage(ctx context.Context, images []*image.Image, imagesUsedbyCtr map[string][]*libpod.Container) ([]imageVerboseDiskUsage, error) {
var imagesVerboseDiskUsage []imageVerboseDiskUsage
imgUniqueSizeMap, err := imageUniqueSize(ctx, images)
if err != nil {
return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting unique size of images")
}
for _, img := range images {
isParent, err := img.IsParent(ctx)
if err != nil {
return imagesVerboseDiskUsage, errors.Wrapf(err, "error checking if %s is a parent images", img.ID())
}
parent, err := img.GetParent(ctx)
if err != nil {
return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID())
}
if isParent && parent != nil {
continue
}
size, err := img.Size(ctx)
if err != nil {
return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting size of image %s", img.ID())
}
numberOfContainers := 0
if ctrs, exist := imagesUsedbyCtr[img.ID()]; exist {
numberOfContainers = len(ctrs)
}
var repo string
var tag string
var repotags []string
if len(img.Names()) != 0 {
repotags = []string{img.Names()[0]}
}
repopairs, err := image.ReposToMap(repotags)
if err != nil {
logrus.Errorf("error finding tag/digest for %s", img.ID())
}
for reponame, tags := range repopairs {
for _, tagname := range tags {
repo = reponame
tag = tagname
}
}
imageVerbosedf := imageVerboseDiskUsage{
Repository: repo,
Tag: tag,
ImageID: shortID(img.ID()),
Created: fmt.Sprintf("%s ago", units.HumanDuration(time.Since((img.Created().Local())))),
Size: units.HumanSizeWithPrecision(float64(*size), 3),
SharedSize: units.HumanSizeWithPrecision(float64(*size-imgUniqueSizeMap[img.ID()]), 3),
UniqueSize: units.HumanSizeWithPrecision(float64(imgUniqueSizeMap[img.ID()]), 3),
Containers: numberOfContainers,
}
imagesVerboseDiskUsage = append(imagesVerboseDiskUsage, imageVerbosedf)
}
return imagesVerboseDiskUsage, nil
}
func getContainerVerboseDiskUsage(containers []*libpod.Container) (containersVerboseDiskUsage []containerVerboseDiskUsage, err error) {
for _, ctr := range containers {
imgID, _ := ctr.Image()
size, err := ctr.RWSize()
if err != nil {
return containersVerboseDiskUsage, errors.Wrapf(err, "error getting size of container %s", ctr.ID())
}
state, err := ctr.State()
if err != nil {
return containersVerboseDiskUsage, errors.Wrapf(err, "error getting the state of container %s", ctr.ID())
}
ctrVerboseData := containerVerboseDiskUsage{
ContainerID: shortID(ctr.ID()),
Image: shortImageID(imgID),
Command: strings.Join(ctr.Command(), " "),
LocalVolumes: len(ctr.UserVolumes()),
Size: units.HumanSizeWithPrecision(float64(size), 3),
Created: fmt.Sprintf("%s ago", units.HumanDuration(time.Since(ctr.CreatedTime().Local()))),
Status: state.String(),
Names: ctr.Name(),
}
containersVerboseDiskUsage = append(containersVerboseDiskUsage, ctrVerboseData)
}
return containersVerboseDiskUsage, nil
}
func getVolumeVerboseDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (volumesVerboseDiskUsage []volumeVerboseDiskUsage, err error) {
for _, vol := range volumes {
volSize, err := volumeSize(vol)
if err != nil {
return volumesVerboseDiskUsage, errors.Wrapf(err, "error getting size of volume %s", vol.Name())
}
links := 0
if linkCtr, exist := volumeUsedByContainerMap[vol.Name()]; exist {
links = len(linkCtr)
}
volumeVerboseData := volumeVerboseDiskUsage{
VolumeName: vol.Name(),
Links: links,
Size: units.HumanSizeWithPrecision(float64(volSize), 3),
}
volumesVerboseDiskUsage = append(volumesVerboseDiskUsage, volumeVerboseData)
}
return volumesVerboseDiskUsage, nil
}
func imagesVerboseOutput(ctx context.Context, metaData dfMetaData) error {
var imageVerboseHeader = map[string]string{
"Repository": "REPOSITORY",
"Tag": "TAG",
"ImageID": "IMAGE ID",
"Created": "CREATED",
"Size": "SIZE",
"SharedSize": "SHARED SIZE",
"UniqueSize": "UNIQUE SIZE",
"Containers": "CONTAINERS",
}
imagesVerboseDiskUsage, err := getImageVerboseDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap)
if err != nil {
return errors.Wrapf(err, "error getting verbose output of images")
}
if _, err := os.Stderr.WriteString("Images space usage:\n\n"); err != nil {
return err
}
out := formats.StdoutTemplateArray{Output: systemDfImageVerboseDiskUsageToGeneric(imagesVerboseDiskUsage), Template: imageVerboseFormat, Fields: imageVerboseHeader}
return out.Out()
}
func containersVerboseOutput(ctx context.Context, metaData dfMetaData) error {
var containerVerboseHeader = map[string]string{
"ContainerID": "CONTAINER ID ",
"Image": "IMAGE",
"Command": "COMMAND",
"LocalVolumes": "LOCAL VOLUMES",
"Size": "SIZE",
"Created": "CREATED",
"Status": "STATUS",
"Names": "NAMES",
}
containersVerboseDiskUsage, err := getContainerVerboseDiskUsage(metaData.containers)
if err != nil {
return errors.Wrapf(err, "error getting verbose output of containers")
}
if _, err := os.Stderr.WriteString("\nContainers space usage:\n\n"); err != nil {
return err
}
out := formats.StdoutTemplateArray{Output: systemDfContainerVerboseDiskUsageToGeneric(containersVerboseDiskUsage), Template: containerVerboseFormat, Fields: containerVerboseHeader}
return out.Out()
}
func volumesVerboseOutput(ctx context.Context, metaData dfMetaData) error {
var volumeVerboseHeader = map[string]string{
"VolumeName": "VOLUME NAME",
"Links": "LINKS",
"Size": "SIZE",
}
volumesVerboseDiskUsage, err := getVolumeVerboseDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap)
if err != nil {
return errors.Wrapf(err, "error getting verbose output of volumes")
}
if _, err := os.Stderr.WriteString("\nLocal Volumes space usage:\n\n"); err != nil {
return err
}
out := formats.StdoutTemplateArray{Output: systemDfVolumeVerboseDiskUsageToGeneric(volumesVerboseDiskUsage), Template: volumeVerboseFormat, Fields: volumeVerboseHeader}
return out.Out()
}
func verboseOutput(ctx context.Context, metaData dfMetaData) error {
if err := imagesVerboseOutput(ctx, metaData); err != nil {
return err
}
if err := containersVerboseOutput(ctx, metaData); err != nil {
return err
}
if err := volumesVerboseOutput(ctx, metaData); err != nil {
return err
}
return nil
}
func systemDfDiskUsageToGeneric(diskUsages []systemDfDiskUsage) (out []interface{}) {
for _, usage := range diskUsages {
out = append(out, interface{}(usage))
}
return out
}
func systemDfImageVerboseDiskUsageToGeneric(diskUsages []imageVerboseDiskUsage) (out []interface{}) {
for _, usage := range diskUsages {
out = append(out, interface{}(usage))
}
return out
}
func systemDfContainerVerboseDiskUsageToGeneric(diskUsages []containerVerboseDiskUsage) (out []interface{}) {
for _, usage := range diskUsages {
out = append(out, interface{}(usage))
}
return out
}
func systemDfVolumeVerboseDiskUsageToGeneric(diskUsages []volumeVerboseDiskUsage) (out []interface{}) {
for _, usage := range diskUsages {
out = append(out, interface{}(usage))
}
return out
}
func shortImageID(id string) string {
const imageIDTruncLength int = 4
if len(id) > imageIDTruncLength {
return id[:imageIDTruncLength]
}
return id
}