diff --git a/server/image_fs_info.go b/server/image_fs_info.go index bfa297a7..2fe49129 100644 --- a/server/image_fs_info.go +++ b/server/image_fs_info.go @@ -1,13 +1,42 @@ package server import ( - "fmt" + "path" "time" + "github.com/containers/storage" + crioStorage "github.com/kubernetes-incubator/cri-o/utils" "golang.org/x/net/context" 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. func (s *Server) ImageFsInfo(ctx context.Context, req *pb.ImageFsInfoRequest) (resp *pb.ImageFsInfoResponse, err error) { const operation = "image_fs_info" @@ -16,5 +45,14 @@ func (s *Server) ImageFsInfo(ctx context.Context, req *pb.ImageFsInfoRequest) (r 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 } diff --git a/utils/filesystem.go b/utils/filesystem.go new file mode 100644 index 00000000..392ef4ec --- /dev/null +++ b/utils/filesystem.go @@ -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") +}