Repository: threefoldtech/zos
Pull request #1592 (2022-02-16 16:29:03)

Storage fix for type detection
This PR started as a fix for issue with seektime detection. Somtimes when it timesout we assume it's an hdd device. which then can cause issues if the device was actually previosuly detected as ssd and used for the cache volume.

The fix was to depend mainly on the ROTA flag reported by lsblk . But then i had to clean up lots of code issues regarding listing


Patch:
diff --git a/pkg/capacity/capacity.go b/pkg/capacity/capacity.go
index 88c69f41..9b8ab3a3 100644
--- a/pkg/capacity/capacity.go
+++ b/pkg/capacity/capacity.go
@@ -61,12 +61,11 @@ func (r *ResourceOracle) Total() (c gridtypes.Capacity, err error) {
 func IsSecureBoot() (bool, error) {
 	// check if node is booted via efi
 	const (
-		efi        = "/sys/firmware/efi"
 		efivars    = "/sys/firmware/efi/efivars"
 		secureBoot = "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c"
 	)
 
-	_, err := os.Stat(efi)
+	_, err := os.Stat(efivars)
 	if os.IsNotExist(err) {
 		// not even booted with uefi
 		return false, nil
diff --git a/pkg/storage/device.go b/pkg/storage/device.go
index bf1b93d1..0cb83042 100644
--- a/pkg/storage/device.go
+++ b/pkg/storage/device.go
@@ -104,7 +104,7 @@ func (m *Module) DeviceAllocate(min gridtypes.Unit) (pkg.Device, error) {
 			continue
 		}
 
-		if hdd.Device().Size() < uint64(min) {
+		if hdd.Device().Size < uint64(min) {
 			continue
 		}
 
diff --git a/pkg/storage/disk.go b/pkg/storage/disk.go
index b6ceccc6..761ba401 100644
--- a/pkg/storage/disk.go
+++ b/pkg/storage/disk.go
@@ -24,6 +24,7 @@ const (
 
 // VDiskPools return a list of all vdisk pools
 func (s *Module) diskPools() ([]string, error) {
+
 	var paths []string
 	for _, pool := range s.ssds {
 		if _, err := pool.Mounted(); err != nil {
diff --git a/pkg/storage/filesystem/btrfs.go b/pkg/storage/filesystem/btrfs.go
index 2152b3e3..a457ffa2 100644
--- a/pkg/storage/filesystem/btrfs.go
+++ b/pkg/storage/filesystem/btrfs.go
@@ -25,18 +25,18 @@ var (
 )
 
 type btrfsPool struct {
-	device Device
+	device DeviceInfo
 	utils  BtrfsUtil
 	name   string
 }
 
 // NewBtrfsPool creates a btrfs pool associated with device.
 // if device does not have a filesystem one is created
-func NewBtrfsPool(device Device) (Pool, error) {
+func NewBtrfsPool(device DeviceInfo) (Pool, error) {
 	return newBtrfsPool(device, executerFunc(run))
 }
 
-func newBtrfsPool(device Device, exe executer) (Pool, error) {
+func newBtrfsPool(device DeviceInfo, exe executer) (Pool, error) {
 	pool := &btrfsPool{
 		device: device,
 		utils:  newUtils(exe),
@@ -49,18 +49,13 @@ func (p *btrfsPool) ID() int {
 	return 0
 }
 
-func (p *btrfsPool) Device() Device {
+func (p *btrfsPool) Device() DeviceInfo {
 	return p.device
 }
 
 func (p *btrfsPool) prepare() error {
-	info, err := p.device.Info()
-	if err != nil {
-		return errors.Wrapf(err, "failed to get device '%s' info", p.device.Path())
-	}
-
-	p.name = info.Label
-	if info.Used() {
+	p.name = p.device.Label
+	if p.device.Used() {
 		// device already have filesystem
 		return nil
 	}
@@ -81,11 +76,11 @@ func (p *btrfsPool) format(ctx context.Context) error {
 
 	args := []string{
 		"-L", name,
-		p.device.Path(),
+		p.device.Path,
 	}
 
 	if _, err := p.utils.run(ctx, "mkfs.btrfs", args...); err != nil {
-		return errors.Wrapf(err, "failed to format device '%s'", p.device.Path())
+		return errors.Wrapf(err, "failed to format device '%s'", p.device.Path)
 	}
 
 	return nil
@@ -96,13 +91,14 @@ func (p *btrfsPool) format(ctx context.Context) error {
 // but instead check if any of the pool devices is mounted
 // under any location
 func (p *btrfsPool) Mounted() (string, error) {
-	info, err := p.device.Info()
+	ctx := context.TODO()
+	mnt, err := p.device.Mountpoint(ctx)
 	if err != nil {
 		return "", err
 	}
 
-	if len(info.Mountpoint) != 0 {
-		return info.Mountpoint, nil
+	if len(mnt) != 0 {
+		return mnt, nil
 	}
 
 	return "", ErrDeviceNotMounted
@@ -128,6 +124,7 @@ func (p *btrfsPool) FsType() string {
 
 // Mount mounts the pool in it's default mount location under /mnt/name
 func (p *btrfsPool) Mount() (string, error) {
+	ctx := context.TODO()
 	mnt, err := p.Mounted()
 	if err == nil {
 		return mnt, nil
@@ -136,13 +133,12 @@ func (p *btrfsPool) Mount() (string, error) {
 	}
 
 	// device is not mounted
-	ctx := context.Background()
 	mnt = p.Path()
 	if err := os.MkdirAll(mnt, 0755); err != nil {
 		return "", err
 	}
 
-	if err := syscall.Mount(p.device.Path(), mnt, "btrfs", 0, ""); err != nil {
+	if err := syscall.Mount(p.device.Path, mnt, "btrfs", 0, ""); err != nil {
 		return "", err
 	}
 
@@ -254,12 +250,7 @@ func (p *btrfsPool) Usage() (usage Usage, err error) {
 		return Usage{}, err
 	}
 
-	info, err := p.device.Info()
-	if err != nil {
-		return Usage{}, err
-	}
-
-	return Usage{Size: info.Size, Used: du.Data.Used}, nil
+	return Usage{Size: p.device.Size, Used: du.Data.Used}, nil
 }
 
 // Type of the physical storage used for this pool
@@ -324,9 +315,9 @@ func (p *btrfsPool) maintenance() error {
 }
 
 func (p *btrfsPool) Shutdown() error {
-	cmd := exec.Command("hdparm", "-y", p.device.Path())
+	cmd := exec.Command("hdparm", "-y", p.device.Path)
 	if err := cmd.Run(); err != nil {
-		return errors.Wrapf(err, "failed to shutdown device '%s'", p.device.Path())
+		return errors.Wrapf(err, "failed to shutdown device '%s'", p.device.Path)
 	}
 
 	return nil
@@ -368,7 +359,7 @@ func (v *btrfsVolume) FsType() string {
 
 // Usage return the volume usage
 func (v *btrfsVolume) Usage() (usage Usage, err error) {
-	ctx := context.Background()
+	ctx := context.TODO()
 	info, err := v.utils.SubvolumeInfo(ctx, v.Path())
 	if err != nil {
 		return usage, err
diff --git a/pkg/storage/filesystem/device.go b/pkg/storage/filesystem/device.go
index c3f5bf01..4811fbbd 100644
--- a/pkg/storage/filesystem/device.go
+++ b/pkg/storage/filesystem/device.go
@@ -5,8 +5,8 @@ import (
 	"encoding/json"
 	"fmt"
 	"os/exec"
+	"path/filepath"
 	"regexp"
-	"strings"
 	"syscall"
 	"time"
 
@@ -15,35 +15,20 @@ import (
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
 )
 
-// Device interface
-type Device interface {
-	// Path returns path to the device like /dev/sda
-	Path() string
-	// Name returns name of the device like sda
-	Name() string
-	// Size device size
-	Size() uint64
-	// Type returns detected device type (hdd, ssd)
-	Type() zos.DeviceType
-	// Info is current device information, this should not be cached because
-	// it might change over time
-	Info() (DeviceInfo, error)
-	// ReadTime detected read time of the device
-	ReadTime() uint64
-}
-
 // DeviceManager is able to list all/specific devices on a system
 type DeviceManager interface {
 	// Device returns the device at the specified path
-	Device(ctx context.Context, device string) (Device, error)
+	Device(ctx context.Context, device string) (DeviceInfo, error)
 	// Devices finds all devices on a system
 	Devices(ctx context.Context) (Devices, error)
 	// ByLabel finds all devices with the specified label
 	ByLabel(ctx context.Context, label string) (Devices, error)
+	// Mountpoint returns mount point of a device
+	Mountpoint(ctx context.Context, device string) (string, error)
 }
 
 // Devices represents a list of cached in memory devices
-type Devices []Device
+type Devices []DeviceInfo
 
 // FSType type of filesystem on device
 type FSType string
@@ -64,92 +49,36 @@ type blockDevices struct {
 
 // DeviceInfo contains information about the device
 type DeviceInfo struct {
+	mgr DeviceManager
+
 	Path       string `json:"path"`
 	Label      string `json:"label"`
 	Size       uint64 `json:"size"`
-	Mountpoint string `json:"mountpoint"`
 	Filesystem FSType `json:"fstype"`
+	Rota       bool   `json:"rota"`
 	Subsystems string `json:"subsystems"`
+	Readtime   uint64 `json:"-"`
 }
 
-// func (i *DeviceInfo) ID() (id string, err error) {
-// 	// check if it's a pool using the label format.
-// 	// if it has a filesystem then it should have the label in correct format
-// 	if _, err = fmt.Sscanf(i.Label, PoolLabelPrefix+"%s", &id); err != nil {
-// 		return id, ErrInvalidLabel
-// 	}
-
-// 	return
-// }
-
-// func (i *DeviceInfo) IsPool() bool {
-// 	id, err := i.ID()
-// 	if err != nil {
-// 		return false
-// 	}
-
-// 	return len(id) != 0
-// }
+func (i *DeviceInfo) Name() string {
+	return filepath.Base(i.Path)
+}
 
 // Used assumes that the device is used if it has custom label or fstype or children
 func (i *DeviceInfo) Used() bool {
 	return len(i.Label) != 0 || len(i.Filesystem) != 0
 }
 
-type deviceImpl struct {
-	minDevice
-	mgr *lsblkDeviceManager
-}
-
-func (d *deviceImpl) Info() (DeviceInfo, error) {
-	var devices struct {
-		BlockDevices []DeviceInfo `json:"blockdevices"`
-	}
-
-	if err := d.mgr.lsblk(context.Background(), &devices, d.IPath); err != nil {
-		return DeviceInfo{}, err
-	}
-	if len(devices.BlockDevices) != 1 {
-		return DeviceInfo{}, fmt.Errorf("device not found")
-	}
-
-	return devices.BlockDevices[0], nil
-}
-
-func (d *deviceImpl) Type() zos.DeviceType {
-	return d.DiskType
-}
-
-func (d *deviceImpl) ReadTime() uint64 {
-	return d.RTime
-}
-
-type minDevice struct {
-	IPath      string         `json:"path"`
-	IName      string         `json:"name"`
-	ISize      uint64         `json:"size"`
-	DiskType   zos.DeviceType `json:"-"`
-	RTime      uint64         `json:"-"`
-	Subsystems string         `json:"subsystems"`
-}
-
-func (m minDevice) toDevice(mgr *lsblkDeviceManager) Device {
-	return &deviceImpl{
-		minDevice: m,
-		mgr:       mgr,
+func (d *DeviceInfo) Type() zos.DeviceType {
+	if d.Rota {
+		return zos.HDDDevice
 	}
-}
-
-func (m *minDevice) Path() string {
-	return m.IPath
-}
 
-func (m *minDevice) Size() uint64 {
-	return m.ISize
+	return zos.SSDDevice
 }
 
-func (m *minDevice) Name() string {
-	return m.IName
+func (d *DeviceInfo) Mountpoint(ctx context.Context) (string, error) {
+	return d.mgr.Mountpoint(ctx, d.Path)
 }
 
 // lsblkDeviceManager uses the lsblk utility to scann the disk for devices, and
@@ -159,15 +88,15 @@ func (m *minDevice) Name() string {
 // method is called.
 type lsblkDeviceManager struct {
 	executer
-	cache []minDevice
+	cache []DeviceInfo
 }
 
 // DefaultDeviceManager returns a default device manager implementation
-func DefaultDeviceManager(ctx context.Context) DeviceManager {
-	return defaultDeviceManager(ctx, executerFunc(run))
+func DefaultDeviceManager() DeviceManager {
+	return defaultDeviceManager(executerFunc(run))
 }
 
-func defaultDeviceManager(ctx context.Context, exec executer) DeviceManager {
+func defaultDeviceManager(exec executer) DeviceManager {
 	m := &lsblkDeviceManager{
 		executer: exec,
 	}
@@ -177,17 +106,7 @@ func defaultDeviceManager(ctx context.Context, exec executer) DeviceManager {
 
 // Devices gets available block devices
 func (l *lsblkDeviceManager) Devices(ctx context.Context) (Devices, error) {
-	devices, err := l.scan(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	result := make(Devices, 0, len(devices))
-	for _, dev := range devices {
-		result = append(result, dev.toDevice(l))
-	}
-
-	return result, nil
+	return l.scan(ctx)
 }
 
 func (l *lsblkDeviceManager) ByLabel(ctx context.Context, label string) (Devices, error) {
@@ -199,12 +118,7 @@ func (l *lsblkDeviceManager) ByLabel(ctx context.Context, label string) (Devices
 	var filtered Devices
 
 	for _, device := range devices {
-		info, err := device.Info()
-		if err != nil {
-			return nil, err
-		}
-
-		if info.Label == label {
+		if device.Label == label {
 			filtered = append(filtered, device)
 		}
 	}
@@ -212,72 +126,60 @@ func (l *lsblkDeviceManager) ByLabel(ctx context.Context, label string) (Devices
 	return filtered, nil
 }
 
-func (l *lsblkDeviceManager) Device(ctx context.Context, path string) (device Device, err error) {
+func (l *lsblkDeviceManager) Device(ctx context.Context, path string) (device DeviceInfo, err error) {
 	devices, err := l.scan(ctx)
 	if err != nil {
-		return nil, err
+		return device, err
 	}
 
 	for _, dev := range devices {
-		if dev.IPath == path {
-			return dev.toDevice(l), nil
+		if dev.Path == path {
+			return dev, nil
 		}
 	}
 
-	return nil, fmt.Errorf("device not found")
+	return device, fmt.Errorf("device not found")
 
 }
 
-func (l *lsblkDeviceManager) lsblk(ctx context.Context, output interface{}, device ...string) error {
+func (l *lsblkDeviceManager) lsblk(ctx context.Context) ([]DeviceInfo, error) {
 	var devices blockDevices
 
 	args := []string{
 		"--json",
 		"-o",
-		"PATH,NAME,SIZE,SUBSYSTEMS,FSTYPE,LABEL",
+		"PATH,NAME,SIZE,SUBSYSTEMS,FSTYPE,LABEL,ROTA",
 		"--bytes",
 		"--exclude",
 		"1,2,11",
 		"--path",
 	}
-	if len(device) == 1 {
-		args = append(args, device[0])
-	} else if len(device) > 1 {
-		return fmt.Errorf("only one device is supported")
-	}
 
 	bytes, err := l.run(ctx, "lsblk", args...)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// skipping unmarshal when lsblk response is empty
 	if len(bytes) == 0 {
-		log.Warn().Msg("no disks found on the system")
-		return nil
+		return nil, nil
 	}
 
 	// parsing lsblk response
 	if err := json.Unmarshal(bytes, &devices); err != nil {
-		return err
-	}
-	// lsblk blocks for blocking file systems
-	if err := l.fillMountpointInfo(ctx, &devices, device...); err != nil {
-		return err
-	}
-	bytes, err = json.Marshal(devices)
-	if err != nil {
-		return err
+		return nil, err
 	}
-	if err := json.Unmarshal(bytes, output); err != nil {
-		return err
+
+	for i := range devices.BlockDevices {
+		devices.BlockDevices[i].mgr = l
 	}
-	return nil
+
+	return devices.BlockDevices, nil
 }
 
-func (l *lsblkDeviceManager) fillMountpointInfo(ctx context.Context, devices *blockDevices, device ...string) error {
+func (l *lsblkDeviceManager) Mountpoint(ctx context.Context, device string) (string, error) {
 	// to not pollute global namespace with ugly hack types
-	type findmntOutput struct {
+	var mounts struct {
 		Filesystems []struct {
 			Source  string
 			Target  string
@@ -286,51 +188,38 @@ func (l *lsblkDeviceManager) fillMountpointInfo(ctx context.Context, devices *bl
 	}
 	args := []string{
 		"-J",
+		"-S", device,
 	}
-	if len(device) == 1 {
-		args = append(args, "-S", device[0])
-	} else if len(device) > 1 {
-		return fmt.Errorf("only one device is supported")
-	}
+
 	bytes, err := l.run(ctx, "findmnt", args...)
 	if err != nil {
 		// empty output and exit code 1 in case the device is not found
-		return nil
+		return "", nil
 	}
-	var mounts findmntOutput
 	if len(bytes) != 0 {
 		if err := json.Unmarshal(bytes, &mounts); err != nil {
-			return err
+			return "", err
 		}
 	}
-	mountpoints := make(map[string]string)
+
 	for _, m := range mounts.Filesystems {
 		if subvolFindmntOption.MatchString(m.Options) {
-			mountpoints[m.Source] = m.Target
-		}
-	}
-	for idx := range devices.BlockDevices {
-		source := devices.BlockDevices[idx].Path
-		if target, ok := mountpoints[source]; ok {
-			devices.BlockDevices[idx].Mountpoint = target
+			return m.Target, nil
 		}
 	}
-	return nil
-
-}
 
-func (l *lsblkDeviceManager) raw(ctx context.Context) ([]minDevice, error) {
+	return "", nil
 
-	var devices struct {
-		BlockDevices []minDevice `json:"blockdevices"`
-	}
+}
 
-	if err := l.lsblk(ctx, &devices); err != nil {
+func (l *lsblkDeviceManager) raw(ctx context.Context) ([]DeviceInfo, error) {
+	devices, err := l.lsblk(ctx)
+	if err != nil {
 		return nil, err
 	}
 
-	filtered := devices.BlockDevices[:0]
-	for _, device := range devices.BlockDevices {
+	filtered := devices[:0]
+	for _, device := range devices {
 		if device.Subsystems != "block:scsi:usb:pci" {
 			filtered = append(filtered, device)
 		}
@@ -340,7 +229,7 @@ func (l *lsblkDeviceManager) raw(ctx context.Context) ([]minDevice, error) {
 }
 
 // scan the system for disks using the `lsblk` command
-func (l *lsblkDeviceManager) scan(ctx context.Context) ([]minDevice, error) {
+func (l *lsblkDeviceManager) scan(ctx context.Context) ([]DeviceInfo, error) {
 	if l.cache != nil {
 		return l.cache, nil
 	}
@@ -350,7 +239,7 @@ func (l *lsblkDeviceManager) scan(ctx context.Context) ([]minDevice, error) {
 		return nil, errors.Wrap(err, "failed to scan devices")
 	}
 
-	if err := l.setDeviceTypes(devs); err != nil {
+	if err := l.setDeviceReadTimes(devs); err != nil {
 		return nil, err
 	}
 
@@ -382,7 +271,6 @@ func (l *lsblkDeviceManager) seektime(ctx context.Context, path string) (string,
 	bytes, err := l.run(ctx, "seektime", "-j", path)
 	if isTimeout(err) {
 		// the seektime is taking too long that's defintely a HDD
-		log.Warn().Str("device", path).Msg("checking readtime for device timedout. assuming HDD")
 		return "HDD", 5 * 60, nil
 	} else if err != nil {
 		return "", 0, err
@@ -399,46 +287,19 @@ func (l *lsblkDeviceManager) seektime(ctx context.Context, path string) (string,
 	return seekTime.Typ, seekTime.Time, err
 }
 
-func (l *lsblkDeviceManager) setDeviceTypes(devices []minDevice) error {
+func (l *lsblkDeviceManager) setDeviceReadTimes(devices []DeviceInfo) error {
 	for idx := range devices {
 		d := &devices[idx]
 
-		typ, rt, err := l.seektime(context.Background(), d.IPath)
+		_, rt, err := l.seektime(context.Background(), d.Path)
 		if err != nil {
 			// don't include errored devices in the result
 			log.Error().Err(err).Msgf("failed to get disk read time")
 			return err
 		}
 
-		l.setDeviceType(d, l.deviceTypeFromString(typ), rt)
+		d.Readtime = rt
 	}
 
 	return nil
 }
-
-// setDeviceType recursively sets a device type and read time on a device and
-// all of its children
-func (l *lsblkDeviceManager) setDeviceType(device *minDevice, typ zos.DeviceType, readTime uint64) {
-	device.DiskType = typ
-	device.RTime = readTime
-
-}
-
-func (l *lsblkDeviceManager) deviceTypeFromString(typ string) zos.DeviceType {
-	switch strings.ToLower(typ) {
-	case string(zos.SSDDevice):
-		return zos.SSDDevice
-	case string(zos.HDDDevice):
-		return zos.HDDDevice
-	default:
-		// if we have an error or unrecognized type, set type to HDD
-		return zos.HDDDevice
-	}
-}
-
-// ByReadTime implements sort.Interface for []Device based on the ReadTime field
-type ByReadTime Devices
-
-func (a ByReadTime) Len() int           { return len(a) }
-func (a ByReadTime) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-func (a ByReadTime) Less(i, j int) bool { return a[i].ReadTime() < a[j].ReadTime() }
diff --git a/pkg/storage/filesystem/filesystem.go b/pkg/storage/filesystem/filesystem.go
index 289199d5..72944b4c 100644
--- a/pkg/storage/filesystem/filesystem.go
+++ b/pkg/storage/filesystem/filesystem.go
@@ -54,7 +54,7 @@ type Pool interface {
 	// Shutdown spins down the device where the pool is mounted
 	Shutdown() error
 	// Device return device associated with pool
-	Device() Device
+	Device() DeviceInfo
 }
 
 // Filter closure for Filesystem list
diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go
index 8dd76e65..44de1562 100644
--- a/pkg/storage/storage.go
+++ b/pkg/storage/storage.go
@@ -55,10 +55,7 @@ type Module struct {
 
 // New create a new storage module service
 func New() (*Module, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
-	defer cancel()
-
-	m := filesystem.DefaultDeviceManager(ctx)
+	m := filesystem.DefaultDeviceManager()
 
 	s := &Module{
 		ssds:          []filesystem.Pool{},
@@ -108,7 +105,7 @@ func (s *Module) dump() {
 			log.Debug().Msgf("pool %s is mounted at: %s", pool.Name(), path)
 		}
 		device := pool.Device()
-		log.Debug().Str("path", device.Path()).Str("label", pool.Name()).Str("type", string(zos.SSDDevice)).Send()
+		log.Debug().Str("path", device.Path).Str("label", pool.Name()).Str("type", string(zos.SSDDevice)).Send()
 	}
 
 	for _, pool := range s.hdds {
@@ -117,7 +114,7 @@ func (s *Module) dump() {
 			log.Debug().Msgf("pool %s is mounted at: %s", pool.Name(), path)
 		}
 		device := pool.Device()
-		log.Debug().Str("path", device.Path()).Str("label", pool.Name()).Str("type", string(zos.HDDDevice)).Send()
+		log.Debug().Str("path", device.Path).Str("label", pool.Name()).Str("type", string(zos.HDDDevice)).Send()
 	}
 
 }
@@ -149,21 +146,23 @@ func (s *Module) initialize() error {
 	}
 
 	for _, device := range devices {
+		log.Debug().Msgf("device: %+v", device)
 		pool, err := filesystem.NewBtrfsPool(device)
 		if err != nil {
-			log.Error().Err(err).Str("device", device.Path()).Msg("failed to create pool on device")
-			s.brokenDevices = append(s.brokenDevices, pkg.BrokenDevice{Path: device.Path(), Err: err})
+			log.Error().Err(err).Str("device", device.Path).Msg("failed to create pool on device")
+			s.brokenDevices = append(s.brokenDevices, pkg.BrokenDevice{Path: device.Path, Err: err})
 			continue
 		}
 
 		_, err = pool.Mount()
 		if err != nil {
+			log.Error().Err(err).Str("device", device.Path).Msg("failed to mount pool")
 			s.brokenPools = append(s.brokenPools, pkg.BrokenPool{Label: pool.Name(), Err: err})
 			continue
 		}
 		usage, err := pool.Usage()
 		if err != nil {
-			log.Error().Err(err).Str("pool", pool.Name()).Str("device", device.Path()).Msg("failed to get usage of pool")
+			log.Error().Err(err).Str("pool", pool.Name()).Str("device", device.Path).Msg("failed to get usage of pool")
 		}
 
 		typ := device.Type()
@@ -171,7 +170,7 @@ func (s *Module) initialize() error {
 			// force ssd device for vms
 			typ = zos.SSDDevice
 
-			if device.Path() == "/dev/vdd" || device.Path() == "/dev/vde" {
+			if device.Path == "/dev/vdd" || device.Path == "/dev/vde" {
 				typ = zos.HDDDevice
 			}
 		}
@@ -511,6 +510,8 @@ func (s *Module) ensureCache() error {
 		return syscall.Mount("", "/var/cache", "tmpfs", 0, "size=500M")
 	}
 
+	_ = app.DeleteFlag(app.LimitedCache)
+
 	log.Info().Msgf("set cache quota to %d GiB", cacheSize/gib)
 	if err := cacheFs.Limit(cacheSize); err != nil {
 		log.Error().Err(err).Msg("failed to set cache quota")
@@ -704,7 +705,7 @@ func (s *Module) Monitor(ctx context.Context) <-chan pkg.PoolsStats {
 
 				var deviceNames []string
 				for _, device := range devices {
-					deviceNames = append(deviceNames, device.Path())
+					deviceNames = append(deviceNames, device.Path)
 				}
 
 				usage, err := disk.UsageWithContext(ctx, pool.Path())
@@ -755,8 +756,8 @@ func (s *Module) shutdownDisks(vm bool) {
 	for _, set := range [][]filesystem.Pool{s.ssds, s.hdds} {
 		for _, pool := range set {
 			device := pool.Device()
-			log.Debug().Msgf("checking device: %s", device.Path())
-			on, err := checkDiskPowerStatus(device.Path())
+			log.Debug().Msgf("checking device: %s", device.Path)
+			on, err := checkDiskPowerStatus(device.Path)
 			if err != nil {
 				log.Err(err).Msgf("error occurred while checking disk power status")
 				continue
@@ -771,10 +772,10 @@ func (s *Module) shutdownDisks(vm bool) {
 				continue
 			}
 
-			log.Debug().Msgf("shutting down device %s because it is not mounted and the device is on", device.Path())
+			log.Debug().Msgf("shutting down device %s because it is not mounted and the device is on", device.Path)
 			err = pool.Shutdown()
 			if err != nil {
-				log.Err(err).Msgf("failed to shutdown device %s", device.Path())
+				log.Err(err).Msgf("failed to shutdown device %s", device.Path)
 				continue
 			}
 		}


Test patch:
diff --git a/pkg/storage/filesystem/btrfs_ci_test.go b/pkg/storage/filesystem/btrfs_ci_test.go
index 0134c9f9..a79af16d 100644
--- a/pkg/storage/filesystem/btrfs_ci_test.go
+++ b/pkg/storage/filesystem/btrfs_ci_test.go
@@ -21,13 +21,11 @@ import (
 	"io/ioutil"
 	"os"
 	"path"
-	"path/filepath"
 	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
 )
 
 var (
@@ -41,10 +39,9 @@ func (d TestDevices) Loops() Devices {
 		executer: executerFunc(run),
 	}
 	for _, loop := range d {
-		mgr.cache = append(mgr.cache, minDevice{
-			IPath:    loop,
-			IName:    filepath.Base(loop),
-			DiskType: zos.SSDDevice,
+		mgr.cache = append(mgr.cache, DeviceInfo{
+			Path: loop,
+			Rota: false,
 		})
 	}
 
diff --git a/pkg/storage/filesystem/btrfs_test.go b/pkg/storage/filesystem/btrfs_test.go
index 5e62f957..c40cd92e 100644
--- a/pkg/storage/filesystem/btrfs_test.go
+++ b/pkg/storage/filesystem/btrfs_test.go
@@ -4,69 +4,54 @@ import (
 	"context"
 	"testing"
 
-	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
-
 	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 )
 
-type TestDevice struct {
-	path       string
-	name       string
-	size       uint64
-	deviceType zos.DeviceType
-	info       DeviceInfo
-	readTime   uint64
-}
-
-// Path returns path to the device like /dev/sda
-func (t *TestDevice) Path() string {
-	return t.path
-}
-
-// Size device size
-func (t *TestDevice) Size() uint64 {
-	return t.size
+type TestDeviceManager struct {
+	mock.Mock
 }
 
-// Name returns name of the device like sda
-func (t *TestDevice) Name() string {
-	return t.name
+// Device returns the device at the specified path
+func (m *TestDeviceManager) Device(ctx context.Context, device string) (DeviceInfo, error) {
+	args := m.Called(ctx, device)
+	return args.Get(0).(DeviceInfo), args.Error(1)
 }
 
-// Type returns detected device type (hdd, ssd)
-func (t *TestDevice) Type() zos.DeviceType {
-	return t.deviceType
+// Devices finds all devices on a system
+func (m *TestDeviceManager) Devices(ctx context.Context) (Devices, error) {
+	args := m.Called(ctx)
+	return args.Get(0).(Devices), args.Error(1)
 }
 
-// Info is current device information, this should not be cached because
-// it might change over time
-func (t *TestDevice) Info() (DeviceInfo, error) {
-	return t.info, nil
+// ByLabel finds all devices with the specified label
+func (m *TestDeviceManager) ByLabel(ctx context.Context, label string) (Devices, error) {
+	args := m.Called(ctx, label)
+	return args.Get(0).(Devices), args.Error(1)
 }
 
-// ReadTime detected read time of the device
-func (t *TestDevice) ReadTime() uint64 {
-	return t.readTime
+// Mountpoint returns mount point of a device
+func (m *TestDeviceManager) Mountpoint(ctx context.Context, device string) (string, error) {
+	args := m.Called(ctx, device)
+	return args.String(0), args.Error(1)
 }
 
 func TestBtrfsCreatePoolExists(t *testing.T) {
 	require := require.New(t)
-
 	exe := &TestExecuter{}
-	dev := TestDevice{
-		path:       "/tmp/disk",
-		name:       "disk",
-		deviceType: zos.SSDDevice,
-		readTime:   0,
-		info: DeviceInfo{
-			Path:       "/tmp/disk",
-			Label:      "some-label",
-			Mountpoint: "/mnt/some-label",
-			Filesystem: BtrfsFSType,
-		},
+
+	mgr := TestDeviceManager{}
+	mgr.On("Mountpoint", mock.Anything, "/tmp/disk").
+		Return("/mnt/some-label", nil)
+	dev := DeviceInfo{
+		mgr:        &mgr,
+		Path:       "/tmp/disk",
+		Rota:       false,
+		Readtime:   0,
+		Label:      "some-label",
+		Filesystem: BtrfsFSType,
 	}
-	pool, err := newBtrfsPool(&dev, exe)
+	pool, err := newBtrfsPool(dev, exe)
 	require.NoError(err)
 	require.NotNil(pool)
 
@@ -74,9 +59,19 @@ func TestBtrfsCreatePoolExists(t *testing.T) {
 	require.NoError(err)
 	require.Equal("/mnt/some-label", mnt)
 
+	mgr = TestDeviceManager{}
+	mgr.On("Mountpoint", mock.Anything, "/tmp/disk").
+		Return("", nil)
+	dev = DeviceInfo{
+		mgr:        &mgr,
+		Path:       "/tmp/disk",
+		Rota:       false,
+		Readtime:   0,
+		Label:      "some-label",
+		Filesystem: BtrfsFSType,
+	}
 	// if not mounted!
-	dev.info.Mountpoint = ""
-	pool, err = newBtrfsPool(&dev, exe)
+	pool, err = newBtrfsPool(dev, exe)
 	require.NoError(err)
 	require.NotNil(pool)
 
@@ -89,22 +84,18 @@ func TestBtrfsCreatePoolNotExist(t *testing.T) {
 	require := require.New(t)
 
 	exe := &TestExecuter{}
-	dev := TestDevice{
-		path:       "/tmp/disk",
-		name:       "disk",
-		deviceType: zos.SSDDevice,
-		readTime:   0,
-		info: DeviceInfo{
-			Path: "/tmp/disk",
-		},
+	dev := DeviceInfo{
+		Path:     "/tmp/disk",
+		Rota:     false,
+		Readtime: 0,
 	}
 
 	// expected formating of the device
 	ctx := context.Background()
-	exe.On("run", ctx, "mkfs.btrfs", "-L", mock.AnythingOfType("string"), dev.path).
+	exe.On("run", ctx, "mkfs.btrfs", "-L", mock.AnythingOfType("string"), dev.Path).
 		Return([]byte{}, nil)
 
-	pool, err := newBtrfsPool(&dev, exe)
+	pool, err := newBtrfsPool(dev, exe)
 	require.NoError(err)
 	require.NotNil(pool)
 }
diff --git a/pkg/storage/filesystem/device_test.go b/pkg/storage/filesystem/device_test.go
index e05b9c66..6c7b1782 100644
--- a/pkg/storage/filesystem/device_test.go
+++ b/pkg/storage/filesystem/device_test.go
@@ -14,23 +14,23 @@ func TestDeviceManagerScan(t *testing.T) {
 	ctx := context.Background()
 
 	// we expect this call to lsblk
-	exec.On("run", ctx, "lsblk", "--json", "-o", "PATH,NAME,SIZE,SUBSYSTEMS,FSTYPE,LABEL", "--bytes", "--exclude", "1,2,11", "--path").
+	exec.On("run", ctx, "lsblk", "--json", "-o", "PATH,NAME,SIZE,SUBSYSTEMS,FSTYPE,LABEL,ROTA", "--bytes", "--exclude", "1,2,11", "--path").
 		Return(TestMap{
 			"blockdevices": []TestMap{
-				{"subsystems": "block:scsi:pci", "path": "/tmp/dev1", "name": "dev1"},
-				{"subsystems": "block:scsi:pci", "path": "/tmp/dev2", "name": "dev2"},
+				{"subsystems": "block:scsi:pci", "path": "/tmp/dev1", "name": "dev1", "label": "test"},
+				{"subsystems": "block:scsi:pci", "path": "/tmp/dev2", "name": "dev2", "label": "test2"},
 			},
 		}.Bytes(), nil)
 
 	// then other calls per device for extended details
-	exec.On("run", ctx, "lsblk", "--json", "-o", "PATH,NAME,SIZE,SUBSYSTEMS,FSTYPE,LABEL", "--bytes", "--exclude", "1,2,11", "--path", "/tmp/dev1").
+	exec.On("run", ctx, "lsblk", "--json", "-o", "PATH,NAME,SIZE,SUBSYSTEMS,FSTYPE,LABEL,ROTA", "--bytes", "--exclude", "1,2,11", "--path", "/tmp/dev1").
 		Return(TestMap{
 			"blockdevices": []TestMap{
 				{"subsystems": "block:scsi:pci", "path": "/tmp/dev1", "name": "dev1", "label": "test"},
 			},
 		}.Bytes(), nil)
 
-	exec.On("run", ctx, "lsblk", "--json", "-o", "PATH,NAME,SIZE,SUBSYSTEMS,FSTYPE,LABEL", "--bytes", "--exclude", "1,2,11", "--path", "/tmp/dev2").
+	exec.On("run", ctx, "lsblk", "--json", "-o", "PATH,NAME,SIZE,SUBSYSTEMS,FSTYPE,LABEL,ROTA", "--bytes", "--exclude", "1,2,11", "--path", "/tmp/dev2").
 		Return(TestMap{
 			"blockdevices": []TestMap{
 				{"subsystems": "block:scsi:pci", "path": "/tmp/dev2", "name": "dev2", "label": "test2"},
@@ -66,7 +66,7 @@ func TestDeviceManagerScan(t *testing.T) {
 	exec.On("run", mock.Anything, "seektime", "-j", "/tmp/dev2").
 		Return([]byte(`{"type": "HDD", "elapsed": 5000}`), nil)
 
-	mgr := defaultDeviceManager(ctx, &exec)
+	mgr := defaultDeviceManager(&exec)
 
 	cached, err := mgr.Devices(ctx)
 	require.NoError(err)
@@ -81,6 +81,6 @@ func TestDeviceManagerScan(t *testing.T) {
 	require.NoError(err)
 	require.Len(filtered, 1)
 
-	require.Equal("/tmp/dev1", filtered[0].Path())
+	require.Equal("/tmp/dev1", filtered[0].Path)
 
 }
diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go
index 5d2cf8e3..5e3842f1 100644
--- a/pkg/storage/storage_test.go
+++ b/pkg/storage/storage_test.go
@@ -115,8 +115,8 @@ func (p *testPool) RemoveVolume(name string) error {
 	return args.Error(1)
 }
 
-func (p *testPool) Device() filesystem.Device {
-	return nil
+func (p *testPool) Device() filesystem.DeviceInfo {
+	return filesystem.DeviceInfo{}
 }
 
 func (p *testPool) Shutdown() error {