Skip to content

Commit

Permalink
Rework pruning to report reclaimed space
Browse files Browse the repository at this point in the history
This change adds code to report the reclaimed space after a prune.
Reclaimed space from volumes, images, and containers is recorded
during the prune call in a PruneReport struct. These structs are
collected into a slice during a system prune and processed afterwards
to calculate the total reclaimed space.

Closes containers#8658

Signed-off-by: Baron Lenardson <[email protected]>
  • Loading branch information
Baron Lenardson committed Dec 31, 2020
1 parent c6c9b45 commit b90f7f9
Show file tree
Hide file tree
Showing 36 changed files with 245 additions and 232 deletions.
13 changes: 10 additions & 3 deletions cmd/podman/system/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
dfilters "github.com/containers/podman/v2/pkg/domain/filters"
"github.com/docker/go-units"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -90,7 +91,7 @@ Are you sure you want to continue? [y/N] `, volumeString)
return err
}
// Print container prune results
err = utils.PrintContainerPruneResults(response.ContainerPruneReport, true)
err = utils.PrintContainerPruneResults(response.ContainerPruneReports, true)
if err != nil {
return err
}
Expand All @@ -101,11 +102,17 @@ Are you sure you want to continue? [y/N] `, volumeString)
}
// Print Volume prune results
if pruneOptions.Volume {
err = utils.PrintVolumePruneResults(response.VolumePruneReport, true)
err = utils.PrintVolumePruneResults(response.VolumePruneReports, true)
if err != nil {
return err
}
}
// Print Images prune results
return utils.PrintImagePruneResults(response.ImagePruneReport, true)
err = utils.PrintImagePruneResults(response.ImagePruneReports, true)
if err != nil {
return err
}

fmt.Printf("Total reclaimed space: %s\n", units.HumanSize((float64)(response.ReclaimedSpace)))
return nil
}
35 changes: 17 additions & 18 deletions cmd/podman/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/entities/reports"
)

// IsDir returns true if the specified path refers to a directory.
Expand Down Expand Up @@ -41,21 +42,21 @@ func PrintPodPruneResults(podPruneReports []*entities.PodPruneReport, heading bo
return errs.PrintErrors()
}

func PrintContainerPruneResults(containerPruneReport *entities.ContainerPruneReport, heading bool) error {
func PrintContainerPruneResults(containerPruneReports []*reports.PruneReport, heading bool) error {
var errs OutputErrors
if heading && (len(containerPruneReport.ID) > 0 || len(containerPruneReport.Err) > 0) {
if heading && (len(containerPruneReports) > 0) {
fmt.Println("Deleted Containers")
}
for k := range containerPruneReport.ID {
fmt.Println(k)
}
for _, v := range containerPruneReport.Err {
errs = append(errs, v)
for _, v := range containerPruneReports {
fmt.Println(v.Id)
if v.Err != nil {
errs = append(errs, v.Err)
}
}
return errs.PrintErrors()
}

func PrintVolumePruneResults(volumePruneReport []*entities.VolumePruneReport, heading bool) error {
func PrintVolumePruneResults(volumePruneReport []*reports.PruneReport, heading bool) error {
var errs OutputErrors
if heading && len(volumePruneReport) > 0 {
fmt.Println("Deleted Volumes")
Expand All @@ -70,18 +71,16 @@ func PrintVolumePruneResults(volumePruneReport []*entities.VolumePruneReport, he
return errs.PrintErrors()
}

func PrintImagePruneResults(imagePruneReport *entities.ImagePruneReport, heading bool) error {
if heading && (len(imagePruneReport.Report.Id) > 0 || len(imagePruneReport.Report.Err) > 0) {
func PrintImagePruneResults(imagePruneReports []*reports.PruneReport, heading bool) error {
if heading {
fmt.Println("Deleted Images")
}
for _, i := range imagePruneReport.Report.Id {
fmt.Println(i)
}
for _, e := range imagePruneReport.Report.Err {
fmt.Fprint(os.Stderr, e.Error()+"\n")
}
if imagePruneReport.Size > 0 {
fmt.Fprintf(os.Stdout, "Size: %d\n", imagePruneReport.Size)
for _, r := range imagePruneReports {
fmt.Println(r.Id)
if r.Err != nil {
fmt.Fprint(os.Stderr, r.Err.Error()+"\n")
}
}

return nil
}
23 changes: 18 additions & 5 deletions libpod/image/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/containers/podman/v2/libpod/events"
"github.com/containers/podman/v2/pkg/domain/entities/reports"
"github.com/containers/podman/v2/pkg/timetype"
"github.com/containers/storage"
"github.com/pkg/errors"
Expand Down Expand Up @@ -110,7 +111,8 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I

// PruneImages prunes dangling and optionally all unused images from the local
// image store
func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) {
func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]*reports.PruneReport, error) {
preports := make([]*reports.PruneReport, 0)
filterFuncs := make([]ImageFilter, 0, len(filter))
for _, f := range filter {
filterSplit := strings.SplitN(f, "=", 2)
Expand All @@ -125,7 +127,6 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
filterFuncs = append(filterFuncs, generatedFunc)
}

pruned := []string{}
prev := 0
for {
toPrune, err := ir.GetPruneImages(ctx, all, filterFuncs)
Expand All @@ -143,6 +144,13 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
if err != nil {
return nil, err
}
nameOrID := img.ID()
s, err := img.Size(ctx)
imgSize := *s
if err != nil {
logrus.Warnf("Failed to collect image size for: %s, %s", nameOrID, err)
imgSize = 0
}
if err := img.Remove(ctx, false); err != nil {
if errors.Cause(err) == storage.ErrImageUsedByContainer {
logrus.Warnf("Failed to prune image %s as it is in use: %v.\nA container associated with containers/storage (e.g., Buildah, CRI-O, etc.) maybe associated with this image.\nUsing the rmi command with the --force option will remove the container and image, but may cause failures for other dependent systems.", img.ID(), err)
Expand All @@ -151,13 +159,18 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
return nil, errors.Wrap(err, "failed to prune image")
}
defer img.newImageEvent(events.Prune)
nameOrID := img.ID()

if len(repotags) > 0 {
nameOrID = repotags[0]
}
pruned = append(pruned, nameOrID)

preports = append(preports, &reports.PruneReport{
Id: nameOrID,
Err: nil,
Size: uint64(imgSize),
})
}

}
return pruned, nil
return preports, nil
}
27 changes: 16 additions & 11 deletions libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/containers/podman/v2/libpod/events"
"github.com/containers/podman/v2/libpod/shutdown"
"github.com/containers/podman/v2/pkg/cgroups"
"github.com/containers/podman/v2/pkg/domain/entities/reports"
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/storage"
"github.com/containers/storage/pkg/stringid"
Expand Down Expand Up @@ -884,9 +885,8 @@ func (r *Runtime) GetExecSessionContainer(id string) (*Container, error) {

// PruneContainers removes stopped and exited containers from localstorage. A set of optional filters
// can be provided to be more granular.
func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int64, map[string]error, error) {
pruneErrors := make(map[string]error)
prunedContainers := make(map[string]int64)
func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) ([]*reports.PruneReport, error) {
preports := make([]*reports.PruneReport, 0)
// We add getting the exited and stopped containers via a filter
containerStateFilter := func(c *Container) bool {
if c.PodID() != "" {
Expand All @@ -906,23 +906,28 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int
filterFuncs = append(filterFuncs, containerStateFilter)
delContainers, err := r.GetContainers(filterFuncs...)
if err != nil {
return nil, nil, err
return nil, err
}
for _, c := range delContainers {
ctr := c
size, err := ctr.RWSize()
report := new(reports.PruneReport)
report.Id = c.ID()
report.Err = nil
report.Size = 0
size, err := c.RWSize()
if err != nil {
pruneErrors[ctr.ID()] = err
report.Err = err
preports = append(preports, report)
continue
}
err = r.RemoveContainer(context.Background(), ctr, false, false)
err = r.RemoveContainer(context.Background(), c, false, false)
if err != nil {
pruneErrors[ctr.ID()] = err
report.Err = err
} else {
prunedContainers[ctr.ID()] = size
report.Size = (uint64)(size)
}
preports = append(preports, report)
}
return prunedContainers, pruneErrors, nil
return preports, nil
}

// MountStorageContainer mounts the storage container's root filesystem
Expand Down
25 changes: 18 additions & 7 deletions libpod/runtime_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/events"
"github.com/containers/podman/v2/pkg/domain/entities/reports"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -133,22 +134,32 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) {
}

// PruneVolumes removes unused volumes from the system
func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) (map[string]error, error) {
reports := make(map[string]error)
func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) ([]*reports.PruneReport, error) {
preports := make([]*reports.PruneReport, 0)
vols, err := r.Volumes(filterFuncs...)
if err != nil {
return nil, err
}

for _, vol := range vols {
report := new(reports.PruneReport)
volSize, err := vol.Size()
if err != nil {
volSize = 0
}
report.Size = volSize
report.Id = vol.Name()
if err := r.RemoveVolume(ctx, vol, false); err != nil {
if errors.Cause(err) != define.ErrVolumeBeingUsed && errors.Cause(err) != define.ErrVolumeRemoved {
reports[vol.Name()] = err
report.Err = err
} else {
// We didn't remove the volume for some reason
continue
}
continue
} else {
vol.newVolumeEvent(events.Prune)
}
vol.newVolumeEvent(events.Prune)
reports[vol.Name()] = nil
preports = append(preports, report)
}
return reports, nil
return preports, nil
}
14 changes: 14 additions & 0 deletions libpod/volume.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package libpod

import (
"os"
"path/filepath"
"time"

"github.com/containers/podman/v2/libpod/define"
Expand Down Expand Up @@ -79,6 +81,18 @@ func (v *Volume) Name() string {
return v.config.Name
}

// Returns the size on disk of volume
func (v *Volume) Size() (uint64, error) {
var size uint64
err := filepath.Walk(v.config.MountPoint, func(path string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
size += (uint64)(info.Size())
}
return err
})
return size, err
}

// Driver retrieves the volume's driver.
func (v *Volume) Driver() string {
return v.config.Driver
Expand Down
30 changes: 5 additions & 25 deletions pkg/api/handlers/compat/containers_prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,13 @@ import (

"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/entities/reports"
"github.com/containers/podman/v2/pkg/domain/filters"
"github.com/docker/docker/api/types"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)

func PruneContainers(w http.ResponseWriter, r *http.Request) {
var (
delContainers []string
space int64
)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)

Expand Down Expand Up @@ -49,36 +44,21 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) {
return
}

prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
report, err := runtime.PruneContainers(filterFuncs)
if err != nil {
utils.InternalServerError(w, err)
return
}
for ctrID, size := range prunedContainers {
if pruneErrors[ctrID] == nil {
space += size
delContainers = append(delContainers, ctrID)
}
}
report := types.ContainersPruneReport{
ContainersDeleted: delContainers,
SpaceReclaimed: uint64(space),
}
utils.WriteResponse(w, http.StatusOK, report)
}

func PruneContainersHelper(w http.ResponseWriter, r *http.Request, filterFuncs []libpod.ContainerFilter) (
*entities.ContainerPruneReport, error) {
[]*reports.PruneReport, error) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
reports, err := runtime.PruneContainers(filterFuncs)
if err != nil {
utils.InternalServerError(w, err)
return nil, err
}

report := &entities.ContainerPruneReport{
Err: pruneErrors,
ID: prunedContainers,
}
return report, nil
return reports, nil
}
10 changes: 6 additions & 4 deletions pkg/api/handlers/compat/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,23 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
filters = append(filters, fmt.Sprintf("%s=%s", k, val))
}
}
pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters)
imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters)
if err != nil {
utils.InternalServerError(w, err)
return
}
for _, p := range pruneCids {
reclaimedSpace := uint64(0)
for _, p := range imagePruneReports {
idr = append(idr, types.ImageDeleteResponseItem{
Deleted: p,
Deleted: p.Id,
})
reclaimedSpace = reclaimedSpace + p.Size
}

// FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden
ipr := types.ImagesPruneReport{
ImagesDeleted: idr,
SpaceReclaimed: 1, // TODO we cannot supply this right now
SpaceReclaimed: reclaimedSpace,
}
utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr})
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/handlers/compat/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,9 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
return
}
prunedIds := make([]string, 0, len(pruned))
for k := range pruned {
for _, v := range pruned {
// XXX: This drops any pruning per-volume error messages on the floor
prunedIds = append(prunedIds, k)
prunedIds = append(prunedIds, v.Id)
}
pruneResponse := docker_api_types.VolumesPruneReport{
VolumesDeleted: prunedIds,
Expand Down
Loading

0 comments on commit b90f7f9

Please sign in to comment.