Merge pull request #1283 from lyft/imagefs-stats-1.9

Implement the stats for the image_fs_info command
This commit is contained in:
Daniel J Walsh 2018-02-05 09:06:31 -05:00 committed by GitHub
commit 5345c6299a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 149 additions and 2 deletions

View file

@ -1,13 +1,42 @@
package server package server
import ( import (
"fmt" "path"
"time" "time"
"github.com/containers/storage"
crioStorage "github.com/kubernetes-incubator/cri-o/utils"
"golang.org/x/net/context" "golang.org/x/net/context"
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
) )
func getStorageFsInfo(store storage.Store) (*pb.FilesystemUsage, error) {
rootPath := store.GraphRoot()
storageDriver := store.GraphDriverName()
imagesPath := path.Join(rootPath, storageDriver+"-images")
deviceName, err := crioStorage.GetDeviceNameFromPath(imagesPath)
uuid, err := crioStorage.GetDeviceUUIDFromPath(deviceName)
if err != nil {
return nil, err
}
bytesUsed, inodesUsed, err := crioStorage.GetDiskUsageStats(imagesPath)
if err != nil {
return nil, err
}
usage := pb.FilesystemUsage{
Timestamp: time.Now().UnixNano(),
StorageId: &pb.StorageIdentifier{uuid},
UsedBytes: &pb.UInt64Value{bytesUsed},
InodesUsed: &pb.UInt64Value{inodesUsed},
}
return &usage, nil
}
// ImageFsInfo returns information of the filesystem that is used to store images. // ImageFsInfo returns information of the filesystem that is used to store images.
func (s *Server) ImageFsInfo(ctx context.Context, req *pb.ImageFsInfoRequest) (resp *pb.ImageFsInfoResponse, err error) { func (s *Server) ImageFsInfo(ctx context.Context, req *pb.ImageFsInfoRequest) (resp *pb.ImageFsInfoResponse, err error) {
const operation = "image_fs_info" const operation = "image_fs_info"
@ -16,5 +45,14 @@ func (s *Server) ImageFsInfo(ctx context.Context, req *pb.ImageFsInfoRequest) (r
recordError(operation, err) recordError(operation, err)
}() }()
return nil, fmt.Errorf("not implemented") store := s.StorageImageServer().GetStore()
fsUsage, err := getStorageFsInfo(store)
if err != nil {
return nil, err
}
return &pb.ImageFsInfoResponse{
ImageFilesystems: []*pb.FilesystemUsage{fsUsage},
}, nil
} }

109
utils/filesystem.go Normal file
View file

@ -0,0 +1,109 @@
package utils
import (
"fmt"
"golang.org/x/sys/unix"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/docker/docker/pkg/mount"
)
// GetDiskUsageStats accepts a path to a directory or file
// and returns the number of bytes and inodes used by the path
func GetDiskUsageStats(path string) (uint64, uint64, error) {
var dirSize, inodeCount uint64
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
fileStat, error := os.Lstat(path)
if error != nil {
if fileStat.Mode()&os.ModeSymlink != 0 {
// Is a symlink; no error should be returned
return nil
}
return error
}
dirSize += uint64(info.Size())
inodeCount++
return nil
})
if err != nil {
return 0, 0, err
}
return dirSize, inodeCount, err
}
// GetDeviceUUIDFromPath accepts a path, and will find the device
// corresponding to the path and return the UUID of that device
func GetDeviceUUIDFromPath(devicePath string) (string, error) {
const dir = "/dev/disk/by-uuid"
if _, err := os.Stat(dir); os.IsNotExist(err) {
return "", nil
}
files, err := ioutil.ReadDir(dir)
if err != nil {
return "", err
}
for _, file := range files {
path := filepath.Join(dir, file.Name())
target, err := os.Readlink(path)
if err != nil {
continue
}
device, err := filepath.Abs(filepath.Join(dir, target))
if err != nil {
return "", fmt.Errorf("failed to resolve the absolute path of %q", filepath.Join(dir, target))
}
if strings.Compare(device, devicePath) == 0 {
return file.Name(), nil
}
}
return "", fmt.Errorf("device path %s not found", devicePath)
}
// GetStattFromPath is a helper function that returns the Stat_t
// object for a given path
func GetStattFromPath(path string) (syscall.Stat_t, error) {
statInfo := syscall.Stat_t{}
err := syscall.Lstat(path, &statInfo)
if err != nil {
return statInfo, err
}
return statInfo, nil
}
// GetDeviceNameFromPath iterates through the mounts and matches
// the one that the provided path is on
func GetDeviceNameFromPath(path string) (string, error) {
statInfo, err := GetStattFromPath(path)
if err != nil {
return "", err
}
mounts, err := mount.GetMounts()
if err != nil {
return "", err
}
queryMajor := int(unix.Major(uint64(statInfo.Dev)))
queryMinor := int(unix.Minor(uint64(statInfo.Dev)))
for _, mount := range mounts {
if mount.Minor == queryMinor && mount.Major == queryMajor {
return mount.Source, nil
}
}
return "", fmt.Errorf("no match found")
}