edgeboxctl/internal/storage/storage.go

315 lines
8.6 KiB
Go

package storage
import (
"fmt"
"strconv"
"os"
"path/filepath"
"strings"
"github.com/edgebox-iot/edgeboxctl/internal/diagnostics"
"github.com/edgebox-iot/edgeboxctl/internal/utils"
"github.com/shirou/gopsutil/disk"
)
// Device : Struct representing a storage device in the system
type Device struct {
ID DeviceIdentifier `json:"id"`
Name string `json:"name"`
Size string `json:"size"`
InUse bool `json:"in_use"`
MainDevice bool `json:"main_device"`
MAJ string `json:"maj"`
MIN string `json:"min"`
RM string `json:"rm"`
RO string `json:"ro"`
Partitions []Partition `json:"partitions"`
Status DeviceStatus `json:"status"`
UsageStat UsageStat `json:"usage_stat"`
}
// DeviceStatus : Struct representing possible storage device statuses (code + description)
type DeviceStatus struct {
ID int `json:"id"`
Description string `json:"description"`
}
// MaybeDevice : Boolean flag for validation of device existance
type MaybeDevice struct {
Device Device `json:"device"`
Valid bool `json:"valid"`
}
type UsageStat struct {
Total uint64 `json:"total"`
Used uint64 `json:"used"`
Free uint64 `json:"free"`
Percent string `json:"percent"`
UsageSplit UsageSplit `json:"usage_split"`
}
type UsageSplit struct {
OS uint64 `json:"os"`
EdgeApps uint64 `json:"edgeapps"`
Buckets uint64 `json:"buckets"`
Others uint64 `json:"others"`
}
// Partition : Struct representing a partition / filesystem (Empty Mountpoint means it is not mounted)
type Partition struct {
ID string `json:"id"`
Size string `json:"size"`
MAJ string `json:"maj"`
MIN string `json:"min"`
RM string `json:"rm"`
RO string `json:"ro"`
Filesystem string `json:"filesystem"`
Mountpoint string `json:"mountpoint"`
UsageStat UsageStat `json:"usage_stat"`
}
type DeviceIdentifier string
const (
DISK_TYPE_SDA DeviceIdentifier = "sda"
DISK_TYPE_MCBLK DeviceIdentifier = "mmcblk0"
DISK_TYPE_VDA DeviceIdentifier = "vda"
MIN_DISK_SIZE int = 1048576 // 1GB in bytes
)
func GetDeviceIdentifier(release_version diagnostics.ReleaseVersion) DeviceIdentifier {
switch release_version {
case diagnostics.CLOUD_VERSION:
return DISK_TYPE_SDA
case diagnostics.PROD_VERSION:
return DISK_TYPE_MCBLK
}
return DISK_TYPE_SDA
}
// GetDevices : Returns a list of all available sotrage devices in structs filled with information
func GetDevices(release_version diagnostics.ReleaseVersion) []Device {
var devices []Device
cmdArgs := []string{"--raw", "--bytes", "--noheadings"}
scanner := utils.ExecAndGetLines("/", "lsblk", cmdArgs)
var currentDevice Device
var currentPartitions []Partition
firstDevice := true
currentDeviceInUseFlag := false
mainDiskID := GetDeviceIdentifier(release_version)
for scanner.Scan() {
// 1 Device is represented here. Extract words in order for filling a Device struct
// Example deviceRawInfo: "mmcblk0 179:0 0 29.7G 0 disk"
deviceRawInfo := strings.Fields(scanner.Text())
majMin := strings.SplitN(deviceRawInfo[1], ":", 2)
isDevice := false
isPartition := false
if deviceRawInfo[5] == "part" {
isDevice = false
isPartition = true
} else if deviceRawInfo[5] == "disk" {
isDevice = true
isPartition = false
}
if isDevice {
// Clean up on the latest device being prepared. Append all partitions found and delete the currentPartitions list afterwards.
// The first device found should not run the cleanup lines below
if !firstDevice {
currentDevice.Partitions = currentPartitions
if !currentDeviceInUseFlag {
currentDevice.Status.ID = 0
currentDevice.Status.Description = "not configured"
}
currentDevice.InUse = currentDeviceInUseFlag
currentDeviceInUseFlag = false
currentPartitions = []Partition{}
size, err := strconv.Atoi(currentDevice.Size)
if err != nil {
size = 0
}
if size > MIN_DISK_SIZE {
devices = append(devices, currentDevice)
}
} else {
firstDevice = false
}
mainDevice := false
device := Device{
ID: DeviceIdentifier(deviceRawInfo[0]),
Name: deviceRawInfo[0],
Size: deviceRawInfo[3],
MainDevice: mainDevice,
MAJ: majMin[0],
MIN: majMin[1],
RM: deviceRawInfo[2],
RO: deviceRawInfo[4],
Status: DeviceStatus{ID: 1, Description: "healthy"},
}
if device.ID == mainDiskID {
device.MainDevice = true
}
currentDevice = device
} else if isPartition {
mountpoint := ""
if len(deviceRawInfo) >= 7 {
mountpoint = deviceRawInfo[6]
currentDeviceInUseFlag = true
}
// It is a partition, part of the last device read.
partition := Partition{
ID: deviceRawInfo[0],
Size: deviceRawInfo[3],
MAJ: majMin[0],
MIN: majMin[1],
RM: deviceRawInfo[2],
RO: deviceRawInfo[4],
Filesystem: "",
Mountpoint: mountpoint,
}
currentPartitions = append(currentPartitions, partition)
} else {
fmt.Println("Found device not compatible with Edgebox, ignoring.")
}
}
currentDevice.Partitions = currentPartitions
if !currentDeviceInUseFlag {
currentDevice.Status.ID = 0
currentDevice.Status.Description = "Not configured"
}
currentDevice.InUse = currentDeviceInUseFlag
fmt.Println("Secondary Storage Devices Found: ", len(devices))
fmt.Println("Main Storage Device size: ", currentDevice.Size)
// only append device if size > 1GB
if currentDevice.Size != "" && currentDevice.Size != "0" {
// Convert size to int
// Convert string to int
size, err := strconv.Atoi(currentDevice.Size)
if err != nil {
size = 0
}
if size > MIN_DISK_SIZE {
devices = append([]Device{currentDevice}, devices...) // Prepending the first device...
}
}
devices = getDevicesSpaceUsage(devices)
return devices
}
func getDevicesSpaceUsage(devices []Device) []Device {
for deviceIndex, device := range devices {
if device.InUse {
deviceUsageStat := UsageStat{}
for partitionIndex, partition := range device.Partitions {
if partition.Mountpoint != "" {
s, _ := disk.Usage(partition.Mountpoint)
if s.Total == 0 {
continue
}
partitionUsagePercent := fmt.Sprintf("%2.f%%", s.UsedPercent)
osUsageSplit := (uint64)(0)
edgeappsUsageSplit := (uint64)(0)
bucketsUsageSplit := (uint64)(0)
othersUsageSplit := (uint64)(0)
edgeappsDirSize, _ := getDirSize(utils.GetPath(utils.EdgeAppsPath))
// TODO for later: Figure out to get correct paths for each partition...
wsAppDataDirSize, _ := getDirSize(utils.GetPath(utils.WsPath) + "/appdata")
if partition.Mountpoint == "/" {
edgeappsUsageSplit = edgeappsDirSize + wsAppDataDirSize
deviceUsageStat.UsageSplit.EdgeApps += edgeappsUsageSplit
}
if device.MainDevice {
osUsageSplit = (s.Used - othersUsageSplit - bucketsUsageSplit - edgeappsUsageSplit)
} else {
othersUsageSplit = (s.Used - bucketsUsageSplit - edgeappsUsageSplit)
}
partitionUsageSplit := UsageSplit{
OS: osUsageSplit,
EdgeApps: edgeappsUsageSplit,
Buckets: bucketsUsageSplit,
Others: othersUsageSplit,
}
deviceUsageStat.Total = deviceUsageStat.Total + s.Total
deviceUsageStat.Used = deviceUsageStat.Used + s.Used
deviceUsageStat.Free = deviceUsageStat.Free + s.Free
deviceUsageStat.UsageSplit.OS = deviceUsageStat.UsageSplit.OS + osUsageSplit
deviceUsageStat.UsageSplit.EdgeApps = deviceUsageStat.UsageSplit.EdgeApps + edgeappsUsageSplit
deviceUsageStat.UsageSplit.Buckets = deviceUsageStat.UsageSplit.Buckets + bucketsUsageSplit
deviceUsageStat.UsageSplit.Others = deviceUsageStat.UsageSplit.Others + othersUsageSplit
devices[deviceIndex].Partitions[partitionIndex].UsageStat = UsageStat{
Total: s.Total,
Used: s.Used,
Free: s.Free,
Percent: partitionUsagePercent,
UsageSplit: partitionUsageSplit,
}
}
}
devices[deviceIndex].UsageStat = deviceUsageStat
totalDevicePercentUsage := fmt.Sprintf("%2.f%%", (float32(devices[deviceIndex].UsageStat.Used)/float32(devices[deviceIndex].UsageStat.Total))*100)
devices[deviceIndex].UsageStat.Percent = totalDevicePercentUsage
}
}
return devices
}
func getDirSize(path string) (uint64, error) {
var size uint64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
size += (uint64)(info.Size())
}
return err
})
return size, err
}