mirror of
https://github.com/edgebox-iot/edgeboxctl.git
synced 2026-09-24 05:41:43 +02:00
Storage Module Basis (#13)
* Added storage module, base structs, getDevices() func, taskGetStorageDevices (ticks every 30) * GetDevices now returns a complete description w/ disk partitions * Added InUse flag to Device, fixed logic bugs * Added disk space usage for partitions in use by the system * Cleanup * Added UsageStat and UsageSplit filling for in use devices * Changed value of lsblk block usage output to bytes * Cleanup, small fixes * Added ExecAndGetLines util func * Added explicit detection of disk or part block type, ignoring non-suppoorted devices * Added command line tool dependencies for the project in README file * Added comment to func ExecAndGetLines * Removed GetMySQLDbConnectionDetails() * Added tests to utils module * Fix invalid argument type for Exec family of tests * Removed too much verbosity from utils.GetPath when no env file is available, added new argument (path) to Exec family of funcs * Added new path argument to utils.Exec... test * Fixed TestExec * Added spaces trim to Exec result * Trimming spaces and newlines from result of Exec * Better visibility for test result log of TestExec * Added ExampleExecAndStream test * Added Example tests with failure status for ExecAndStream * Added branch to test for valid key in GetPath * Added missing t.Fail() statements in utils_test.go * Added storage_test.go file, smoke test * TestGetDevices
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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 string `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"`
|
||||
}
|
||||
|
||||
const mainDiskID = "mmcblk0"
|
||||
|
||||
// GetDevices : Returns a list of all available sotrage devices in structs filled with information
|
||||
func GetDevices() []Device {
|
||||
|
||||
var devices []Device
|
||||
|
||||
cmdArgs := []string{"--raw", "--bytes", "--noheadings"}
|
||||
scanner := utils.ExecAndGetLines("/", "lsblk", cmdArgs)
|
||||
|
||||
var currentDevice Device
|
||||
var currentPartitions []Partition
|
||||
|
||||
firstDevice := true
|
||||
currentDeviceInUseFlag := false
|
||||
|
||||
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{}
|
||||
devices = append(devices, currentDevice)
|
||||
} else {
|
||||
firstDevice = false
|
||||
}
|
||||
|
||||
mainDevice := false
|
||||
|
||||
device := Device{
|
||||
ID: 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
|
||||
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("edgeAppsPath"))
|
||||
// TODO for later: Figure out to get correct paths for each partition...
|
||||
wsAppDataDirSize, _ := getDirSize("/home/system/components/ws/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
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// +build unit
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetDevices(t *testing.T) {
|
||||
result := GetDevices()
|
||||
|
||||
if len(result) == 0 {
|
||||
t.Log("Expecting at least 1 block device, 0 elements found in slice")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
foundDevice := false
|
||||
|
||||
t.Log("Looking for a mmcblk0 or sda device")
|
||||
for _, device := range result {
|
||||
if device.ID == "mmcblk0" || device.ID == "sda" {
|
||||
t.Log("Found target device", device.ID)
|
||||
foundDevice = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundDevice {
|
||||
t.Log("Expected to find device mmcblk0 but did not. Devices:", result)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user