Ver código fonte

Add /system/df API endpoint

This endpoint return data regarding the space used by docker on disk

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
Kenfe-Mickael Laventure 8 anos atrás
pai
commit
f2e11fb8d1

+ 1 - 0
api/server/router/system/backend.go

@@ -14,6 +14,7 @@ import (
 type Backend interface {
 	SystemInfo() (*types.Info, error)
 	SystemVersion() types.Version
+	SystemDiskUsage() (*types.DiskUsage, error)
 	SubscribeToEvents(since, until time.Time, ef filters.Args) ([]events.Message, chan interface{})
 	UnsubscribeFromEvents(chan interface{})
 	AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error)

+ 1 - 0
api/server/router/system/system.go

@@ -26,6 +26,7 @@ func NewRouter(b Backend, c *cluster.Cluster) router.Router {
 		router.Cancellable(router.NewGetRoute("/events", r.getEvents)),
 		router.NewGetRoute("/info", r.getInfo),
 		router.NewGetRoute("/version", r.getVersion),
+		router.NewGetRoute("/system/df", r.getDiskUsage),
 		router.NewPostRoute("/auth", r.postAuth),
 	}
 

+ 9 - 0
api/server/router/system/system_routes.go

@@ -56,6 +56,15 @@ func (s *systemRouter) getVersion(ctx context.Context, w http.ResponseWriter, r
 	return httputils.WriteJSON(w, http.StatusOK, info)
 }
 
+func (s *systemRouter) getDiskUsage(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	du, err := s.backend.SystemDiskUsage()
+	if err != nil {
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, du)
+}
+
 func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 		return err

+ 9 - 0
api/types/types.go

@@ -530,3 +530,12 @@ type Runtime struct {
 	Path string   `json:"path"`
 	Args []string `json:"runtimeArgs,omitempty"`
 }
+
+// DiskUsage contains response of Remote API:
+// GET "/system/df"
+type DiskUsage struct {
+	LayersSize int64
+	Images     []*Image
+	Containers []*Container
+	Volumes    []*Volume
+}

+ 100 - 0
daemon/disk_usage.go

@@ -0,0 +1,100 @@
+package daemon
+
+import (
+	"fmt"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution/digest"
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/layer"
+	"github.com/docker/docker/pkg/directory"
+	"github.com/docker/docker/volume"
+)
+
+func (daemon *Daemon) getLayerRefs() map[layer.ChainID]int {
+	tmpImages := daemon.imageStore.Map()
+	layerRefs := map[layer.ChainID]int{}
+	for id, img := range tmpImages {
+		dgst := digest.Digest(id)
+		if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
+			continue
+		}
+
+		rootFS := *img.RootFS
+		rootFS.DiffIDs = nil
+		for _, id := range img.RootFS.DiffIDs {
+			rootFS.Append(id)
+			chid := rootFS.ChainID()
+			layerRefs[chid]++
+		}
+	}
+
+	return layerRefs
+}
+
+// SystemDiskUsage returns information about the daemon data disk usage
+func (daemon *Daemon) SystemDiskUsage() (*types.DiskUsage, error) {
+	// Retrieve container list
+	allContainers, err := daemon.Containers(&types.ContainerListOptions{
+		Size: true,
+		All:  true,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve container list: %v", err)
+	}
+
+	// Get all top images with extra attributes
+	allImages, err := daemon.Images("", "", false, true)
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve image list: %v", err)
+	}
+
+	// Get all local volumes
+	allVolumes := []*types.Volume{}
+	getLocalVols := func(v volume.Volume) error {
+		name := v.Name()
+		refs := daemon.volumes.Refs(v)
+
+		tv := volumeToAPIType(v)
+		tv.RefCount = len(refs)
+		sz, err := directory.Size(v.Path())
+		if err != nil {
+			logrus.Warnf("failed to determine size of volume %v", name)
+			sz = -1
+		}
+		tv.Size = sz
+		allVolumes = append(allVolumes, tv)
+
+		return nil
+	}
+
+	err = daemon.traverseLocalVolumes(getLocalVols)
+	if err != nil {
+		return nil, err
+	}
+
+	// Get total layers size on disk
+	layerRefs := daemon.getLayerRefs()
+	allLayers := daemon.layerStore.Map()
+	var allLayersSize int64
+	for _, l := range allLayers {
+		size, err := l.DiffSize()
+		if err == nil {
+			if _, ok := layerRefs[l.ChainID()]; ok {
+				allLayersSize += size
+			} else {
+				logrus.Warnf("found leaked image layer %v", l.ChainID())
+			}
+		} else {
+			logrus.Warnf("failed to get diff size for layer %v", l.ChainID())
+		}
+
+	}
+
+	return &types.DiskUsage{
+		LayersSize: allLayersSize,
+		Containers: allContainers,
+		Volumes:    allVolumes,
+		Images:     allImages,
+	}, nil
+}

+ 28 - 0
daemon/volumes.go

@@ -7,12 +7,14 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/Sirupsen/logrus"
 	dockererrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	containertypes "github.com/docker/docker/api/types/container"
 	mounttypes "github.com/docker/docker/api/types/mount"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/volume"
+	"github.com/docker/docker/volume/drivers"
 	"github.com/opencontainers/runc/libcontainer/label"
 )
 
@@ -276,3 +278,29 @@ func backportMountSpec(container *container.Container) error {
 	}
 	return container.ToDiskLocking()
 }
+
+func (daemon *Daemon) traverseLocalVolumes(fn func(volume.Volume) error) error {
+	localVolumeDriver, err := volumedrivers.GetDriver(volume.DefaultDriverName)
+	if err != nil {
+		return fmt.Errorf("can't retrieve local volume driver: %v", err)
+	}
+	vols, err := localVolumeDriver.List()
+	if err != nil {
+		return fmt.Errorf("can't retrieve local volumes: %v", err)
+	}
+
+	for _, v := range vols {
+		name := v.Name()
+		_, err := daemon.volumes.Get(name)
+		if err != nil {
+			logrus.Warnf("failed to retrieve volume %s from store: %v", name, err)
+		}
+
+		err = fn(v)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}