Explorar o código

Added working raid endpoints

aroz hai 1 ano
pai
achega
d6dc8d7ea7

+ 7 - 0
disk.go

@@ -136,6 +136,13 @@ func DiskServiceInit() {
 				//Unable to start RAID manager. Skip it.
 				systemWideLogger.PrintAndLog("RAID", "Unable to start RAID manager", err)
 			}
+
+			if raidManager != nil {
+				authRouter.HandleFunc("/system/disk/raid/list", raidManager.HandleListRaidDevices)
+				authRouter.HandleFunc("/system/disk/raid/remove", raidManager.HandleRemoveRaideDevice)
+				authRouter.HandleFunc("/system/disk/raid/assemble", raidManager.HandleRaidDevicesAssemble)
+				authRouter.HandleFunc("/system/disk/raid/new", raidManager.HandleCreateRAIDDevice)
+			}
 		}
 
 		/*

+ 195 - 0
mod/disk/raid/handler.go

@@ -0,0 +1,195 @@
+package raid
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	"imuslab.com/arozos/mod/utils"
+)
+
+/*
+	Handler.go
+
+	This module handle api call to the raid module
+*/
+
+// List all the raid device in this system
+func (m *Manager) HandleListRaidDevices(w http.ResponseWriter, r *http.Request) {
+	rdev, err := m.GetRAIDDevicesFromProcMDStat()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	js, _ := json.Marshal(rdev)
+	utils.SendJSONResponse(w, string(js))
+}
+
+// Create a RAID storage pool
+func (m *Manager) HandleCreateRAIDDevice(w http.ResponseWriter, r *http.Request) {
+	devName, err := utils.GetPara(r, "devName")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid device name given")
+		return
+	}
+	raidName, err := utils.GetPara(r, "raidName")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid raid storage name given")
+		return
+	}
+	raidLevelStr, err := utils.GetPara(r, "level")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid raid level given")
+		return
+	}
+
+	raidDevicesJSON, err := utils.GetPara(r, "raidDev")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid raid device array given")
+		return
+	}
+
+	spareDevicesJSON, err := utils.GetPara(r, "spareDev")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid spare device array given")
+		return
+	}
+
+	//Convert raidDevices and spareDevices ID into string slice
+	raidDevices := []string{}
+	spareDevices := []string{}
+
+	err = json.Unmarshal([]byte(raidDevicesJSON), &raidDevices)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to parse raid device into array")
+		return
+	}
+
+	err = json.Unmarshal([]byte(spareDevicesJSON), &spareDevices)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to parse spare devices into array")
+		return
+	}
+
+	//Make sure RAID Name do not contain spaces or werid charcters
+	if strings.Contains(raidName, " ") {
+		utils.SendErrorResponse(w, "raid name cannot contain space")
+		return
+	}
+
+	//Convert raidLevel to int
+	raidLevel, err := strconv.Atoi(raidLevelStr)
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid raid level given")
+		return
+	}
+
+	//Create the RAID device
+	err = m.CreateRAIDDevice(devName, raidName, raidLevel, raidDevices, spareDevices)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Update the mdadm config
+	err = m.UpdateMDADMConfig()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+}
+
+// Request to reload the RAID manager and scan new / fix missing raid pools
+func (m *Manager) HandleRaidDevicesAssemble(w http.ResponseWriter, r *http.Request) {
+	err := m.RestartRAIDService()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+}
+
+// Remove a given raid device with its name, USE WITH CAUTION
+func (m *Manager) HandleRemoveRaideDevice(w http.ResponseWriter, r *http.Request) {
+	//TODO: Add protection and switch to POST
+	targetDevice, err := utils.GetPara(r, "dev")
+	if err != nil {
+		utils.SendErrorResponse(w, "target device not given")
+		return
+	}
+
+	//Check if the raid device exists
+	if !m.RAIDDeviceExists(targetDevice) {
+		utils.SendErrorResponse(w, "target device not exists")
+		return
+	}
+
+	//Get the RAID device memeber disks
+	targetRAIDDevice, err := m.GetRAIDDeviceByDevicePath(targetDevice)
+	if err != nil {
+		utils.SendErrorResponse(w, "error occured when trying to load target RAID device info")
+		return
+	}
+
+	//Check if it is mounted. If yes, unmount it
+	if !strings.HasPrefix("/dev/", targetDevice) {
+		targetDevice = filepath.Join("/dev/", targetDevice)
+	}
+
+	mounted, err := DeviceIsMounted(targetDevice)
+	if err != nil {
+		log.Println("[RAID] Unmount failed: " + err.Error())
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	if mounted {
+		err = UnmountDevice(targetDevice)
+		if err != nil {
+			log.Println("[RAID] Unmount failed: " + err.Error())
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+	}
+
+	//Stop & Remove RAID service on the target device
+	err = m.StopRAIDDevice(targetDevice)
+	if err != nil {
+		log.Println("[RAID] Stop RAID partition failed: " + err.Error())
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Zeroblock the RAID device member disks
+	for _, memberDisk := range targetRAIDDevice.Members {
+		//Member disk name do not contain full path
+		name := memberDisk.Name
+		if !strings.HasPrefix(name, "/dev/") {
+			name = filepath.Join("/dev/", name)
+		}
+
+		err = m.ClearSuperblock(name)
+		if err != nil {
+			log.Println("[RAID] Unable to clear superblock on device " + name)
+			continue
+		}
+	}
+
+	//Update the mdadm config
+	//TODO: Validate this actually works
+	err = m.UpdateMDADMConfig()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Done
+	utils.SendOK(w)
+}

+ 112 - 63
mod/disk/raid/mdadm.go

@@ -1,12 +1,16 @@
 package raid
 
 import (
+	"errors"
 	"fmt"
 	"log"
 	"os"
 	"os/exec"
+	"sort"
 	"strconv"
 	"strings"
+
+	"imuslab.com/arozos/mod/utils"
 )
 
 /*
@@ -15,6 +19,7 @@ import (
 	This script handles the interaction with mdadm
 */
 
+// Storage Device meta was generated by lsblk
 type StorageDeviceMeta struct {
 	Name       string
 	Size       int64
@@ -23,6 +28,19 @@ type StorageDeviceMeta struct {
 	MountPoint string
 }
 
+// RAIDDevice represents information about a RAID device.
+type RAIDMember struct {
+	Name string
+	Seq  int
+}
+
+type RAIDDevice struct {
+	Name    string
+	Status  string
+	Level   string
+	Members []*RAIDMember
+}
+
 // List all the storage device in the system, set minSize to 0 for no filter
 func (m *Manager) ListAllStorageDevices(minSize int64) ([]*StorageDeviceMeta, error) {
 	cmd := exec.Command("sudo", "lsblk", "-b")
@@ -96,6 +114,11 @@ func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel in
 	raidDev := len(raidDeviceIds)
 	spareDevice := len(spareDeviceIds)
 
+	//Validate if raid level
+	if !IsValidRAIDLevel("raid" + strconv.Itoa(raidLevel)) {
+		return fmt.Errorf("invalid or unsupported raid level given: raid" + strconv.Itoa(raidLevel))
+	}
+
 	//Validate the number of disk is enough for the raid
 	if raidLevel == 0 && raidDev < 2 {
 		return fmt.Errorf("not enough disks for raid0")
@@ -107,6 +130,16 @@ func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel in
 		return fmt.Errorf("not enough disk for raid6")
 	}
 
+	//Append /dev to the name if missing
+	if !strings.HasPrefix(devName, "/dev/") {
+		devName = "/dev/" + devName
+	}
+
+	if utils.FileExists(devName) {
+		//RAID device already exists
+		return errors.New(devName + " already been used")
+	}
+
 	// Concatenate RAID and spare device arrays
 	allDeviceIds := append(raidDeviceIds, spareDeviceIds...)
 
@@ -125,86 +158,102 @@ func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel in
 	return nil
 }
 
-// Updates the mdadm configuration file with the details of RAID arrays
-// so the RAID drive will still be seen after a reboot (hopefully)
-// this function will not remove any record from the config file. If you need to remove
-// a RAID volume from config, call to RemoveVolmeFromMDADMConfig()
-func UpdateMDADMConfig() error {
-	cmdMdadm := exec.Command("sudo", "mdadm", "--detail", "--scan", "--verbose")
+// GetRAIDDevicesFromProcMDStat retrieves information about RAID devices from /proc/mdstat.
+// if your RAID array is in auto-read-only mode, it is (usually) brand new
+func (m *Manager) GetRAIDDevicesFromProcMDStat() ([]RAIDDevice, error) {
+	// Execute the cat command to read /proc/mdstat
+	cmd := exec.Command("cat", "/proc/mdstat")
 
 	// Run the command and capture its output
-	output, err := cmdMdadm.Output()
+	output, err := cmd.Output()
 	if err != nil {
-		return fmt.Errorf("error running mdadm command: %v", err)
+		return nil, fmt.Errorf("error running cat command: %v", err)
 	}
 
-	//Load the config from system
-	currentConfigBytes, err := os.ReadFile("/etc/mdadm/mdadm.conf")
-	if err != nil {
-		return fmt.Errorf("unable to open mdadm.conf: " + err.Error())
-	}
-	currentConf := string(currentConfigBytes)
-
-	//Check if the current config already contains the setting
-	newConfigLines := []string{}
-	arrayConfigs := strings.TrimSpace(string(output))
-	lines := strings.Split(arrayConfigs, "ARRAY")
-	for _, line := range lines {
-		//For each line, you should have something like this
-		//ARRAY /dev/md0 metadata=1.2 name=debian:0 UUID=cbc11a2b:fbd42653:99c1340b:9c4962fb
-		//   devices=/dev/sdb,/dev/sdc
-
-		line = strings.ReplaceAll(line, "\n", " ")
-		fields := strings.Fields(line)
-		if len(fields) < 5 {
+	// Convert the output to a string and split it into lines
+	lines := strings.Split(string(output), "\n")
+
+	// Initialize an empty slice to store RAID devices
+	raidDevices := make([]RAIDDevice, 0)
+
+	// Iterate over the lines, skipping the first line (Personalities)
+	// Lines usually looks like this
+	// md0 : active raid1 sdc[1] sdb[0]
+	for _, line := range lines[1:] {
+		// Skip empty lines
+		if line == "" {
 			continue
 		}
-		poolUUID := strings.TrimPrefix(fields[3], "UUID=")
 
-		//Check if this uuid already in the config file
-		if strings.Contains(currentConf, poolUUID) {
+		// Split the line by colon (:)
+		parts := strings.SplitN(line, " : ", 2)
+		if len(parts) != 2 {
 			continue
 		}
 
-		//This config not exists in the settings. Add it to append lines
-		settingLine := "ARRAY " + strings.Join(fields, " ")
-		newConfigLines = append(newConfigLines, settingLine)
-
-	}
+		// Extract device name and status
+		deviceName := parts[0]
 
-	if len(newConfigLines) == 0 {
-		//Nothing to write
-		log.Println("Nothing to write. Skipping mdadm config update.")
-		return nil
-	}
+		// Split the members string by space to get individual member devices
+		info := strings.Fields(parts[1])
+		if len(info) < 4 {
+			//Malform output
+			continue
+		}
 
-	// Construct the bash command to append the line to mdadm.conf using echo and tee
-	for _, configLine := range newConfigLines {
-		cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "%s" | sudo tee -a /etc/mdadm/mdadm.conf`, configLine))
+		deviceStatus := info[0]
 
-		// Run the command
-		err := cmd.Run()
-		if err != nil {
-			return fmt.Errorf("error injecting line into mdadm.conf: %v", err)
+		//Raid level usually appears at position 1 - 2, check both
+		raidLevel := ""
+		if strings.HasPrefix(info[1], "raid") {
+			raidLevel = info[1]
+		} else if strings.HasPrefix(info[2], "raid") {
+			raidLevel = info[2]
 		}
-	}
 
-	return nil
-}
-
-// Removes a RAID volume from the mdadm configuration file given its volume UUID.
-// Note that this only remove a single line of config. If your line consists of multiple lines
-// you might need to remove it manually
-func RemoveVolumeFromMDADMConfig(volumeUUID string) error {
-	// Construct the sed command to remove the line containing the volume UUID from mdadm.conf
-	sedCommand := fmt.Sprintf(`sudo sed -i '/UUID=%s/d' /etc/mdadm/mdadm.conf`, volumeUUID)
+		//Get the members (disks) of the array
+		members := []*RAIDMember{}
+		for _, disk := range info[2:] {
+			if !strings.HasPrefix(disk, "sd") {
+				//Probably not a storage device
+				continue
+			}
+
+			//In sda[0] format, we need to split out the number from the disk seq
+			tmp := strings.Split(disk, "[")
+			if len(tmp) != 2 {
+				continue
+			}
+
+			//Convert the sequence to id
+			seqInt, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(tmp[1]), "]"))
+			if err != nil {
+				//Not an integer?
+				log.Println("[RAID] Unable to parse " + disk + " sequence ID")
+				continue
+			}
+			member := RAIDMember{
+				Name: strings.TrimSpace(tmp[0]),
+				Seq:  seqInt,
+			}
+
+			members = append(members, &member)
+		}
 
-	// Execute the sed command
-	cmd := exec.Command("bash", "-c", sedCommand)
-	err := cmd.Run()
-	if err != nil {
-		return fmt.Errorf("error removing volume from mdadm.conf: %v", err)
+		//Sort the member disks
+		sort.Slice(members[:], func(i, j int) bool {
+			return members[i].Seq < members[j].Seq
+		})
+
+		// Create a RAIDDevice struct and append it to the slice
+		raidDevice := RAIDDevice{
+			Name:    deviceName,
+			Status:  deviceStatus,
+			Level:   raidLevel,
+			Members: members,
+		}
+		raidDevices = append(raidDevices, raidDevice)
 	}
 
-	return nil
+	return raidDevices, nil
 }

+ 138 - 0
mod/disk/raid/mdadmConf.go

@@ -0,0 +1,138 @@
+package raid
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"os/exec"
+	"strings"
+
+	"imuslab.com/arozos/mod/utils"
+)
+
+/*
+	mdadmConf.go
+
+	This package handles the config modification and update for
+	the mdadm module
+
+
+*/
+
+// Updates the mdadm configuration file with the details of RAID arrays
+// so the RAID drive will still be seen after a reboot (hopefully)
+func (m *Manager) UpdateMDADMConfig() error {
+	cmdMdadm := exec.Command("sudo", "mdadm", "--detail", "--scan", "--verbose")
+
+	// Run the command and capture its output
+	output, err := cmdMdadm.Output()
+	if err != nil {
+		return fmt.Errorf("error running mdadm command: %v", err)
+	}
+
+	//Load the config from system
+	currentConfigBytes, err := os.ReadFile("/etc/mdadm/mdadm.conf")
+	if err != nil {
+		return fmt.Errorf("unable to open mdadm.conf: " + err.Error())
+	}
+	currentConf := string(currentConfigBytes)
+
+	//Check if the current config already contains the setting
+	newConfigLines := []string{}
+	uuidsInNewConfig := []string{}
+	arrayConfigs := strings.TrimSpace(string(output))
+	lines := strings.Split(arrayConfigs, "ARRAY")
+	for _, line := range lines {
+		//For each line, you should have something like this
+		//ARRAY /dev/md0 metadata=1.2 name=debian:0 UUID=cbc11a2b:fbd42653:99c1340b:9c4962fb
+		//   devices=/dev/sdb,/dev/sdc
+		//Building structure for RAID Config Record
+
+		line = strings.ReplaceAll(line, "\n", " ")
+		fields := strings.Fields(line)
+		if len(fields) < 5 {
+			continue
+		}
+		poolUUID := strings.TrimPrefix(fields[3], "UUID=")
+		uuidsInNewConfig = append(uuidsInNewConfig, poolUUID)
+		//Check if this uuid already in the config file
+		if strings.Contains(currentConf, poolUUID) {
+			continue
+		}
+
+		//This config not exists in the settings. Add it to append lines
+		log.Println("[RAID] Adding " + fields[0] + " (UUID=" + poolUUID + ") into mdadm config")
+		settingLine := "ARRAY " + strings.Join(fields, " ")
+		newConfigLines = append(newConfigLines, settingLine)
+	}
+
+	originalConfigLines := strings.Split(strings.TrimSpace(currentConf), "\n")
+	poolUUIDToBeRemoved := []string{}
+	for _, line := range originalConfigLines {
+		lineFields := strings.Fields(line)
+		for _, thisField := range lineFields {
+			if strings.HasPrefix(thisField, "UUID=") {
+				//This is the UUID of this array. Check if it still exists in new storage config
+				thisPoolUUID := strings.TrimPrefix(thisField, "UUID=")
+				existsInNewConfig := utils.StringInArray(uuidsInNewConfig, thisPoolUUID)
+				if !existsInNewConfig {
+					//Label this UUID to be removed
+					poolUUIDToBeRemoved = append(poolUUIDToBeRemoved, thisPoolUUID)
+				}
+
+				//Skip scanning the remaining fields of this RAID pool
+				break
+			}
+		}
+	}
+
+	if len(poolUUIDToBeRemoved) > 0 {
+		//Remove the old UUIDs from config
+		for _, volumeUUID := range poolUUIDToBeRemoved {
+			err = m.RemoveVolumeFromMDADMConfig(volumeUUID)
+			if err != nil {
+				log.Println("[RAID] Error when trying to remove old RAID volume from config: " + err.Error())
+				return err
+			} else {
+				log.Println("[RAID] RAID volume " + volumeUUID + " removed from config file")
+			}
+		}
+
+	}
+
+	if len(newConfigLines) == 0 {
+		//Nothing to write
+		log.Println("[RAID] Nothing to write. Skipping mdadm config update.")
+		return nil
+	}
+
+	// Construct the bash command to append the line to mdadm.conf using echo and tee
+	for _, configLine := range newConfigLines {
+		cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "%s" | sudo tee -a /etc/mdadm/mdadm.conf`, configLine))
+
+		// Run the command
+		err := cmd.Run()
+		if err != nil {
+			return fmt.Errorf("error injecting line into mdadm.conf: %v", err)
+		}
+	}
+
+	return nil
+}
+
+// Removes a RAID volume from the mdadm configuration file given its volume UUID.
+// Note that this only remove a single line of config. If your line consists of multiple lines
+// you might need to remove it manually
+func (m *Manager) RemoveVolumeFromMDADMConfig(volumeUUID string) error {
+	// Construct the sed command to remove the line containing the volume UUID from mdadm.conf
+	sedCommand := fmt.Sprintf(`sudo sed -i '/UUID=%s/d' /etc/mdadm/mdadm.conf`, volumeUUID)
+
+	// Execute the sed command
+	cmd := exec.Command("bash", "-c", sedCommand)
+	err := cmd.Run()
+	if err != nil {
+		return fmt.Errorf("error removing volume from mdadm.conf: %v", err)
+	}
+
+	return nil
+}

+ 2 - 6
mod/disk/raid/raid_test.go

@@ -7,12 +7,6 @@ package raid_test
 	ONLY RUN IN VM ENVIRONMENT
 */
 
-import (
-	"testing"
-
-	"imuslab.com/arozos/mod/disk/raid"
-)
-
 /*
 func TestRemoveRAIDFromConfig(t *testing.T) {
 	err := raid.RemoveVolumeFromMDADMConfig("cbc11a2b:fbd42653:99c1340b:9c4962fb")
@@ -23,6 +17,7 @@ func TestRemoveRAIDFromConfig(t *testing.T) {
 }
 */
 
+/*
 func TestAddRAIDToConfig(t *testing.T) {
 	err := raid.UpdateMDADMConfig()
 	if err != nil {
@@ -30,6 +25,7 @@ func TestAddRAIDToConfig(t *testing.T) {
 		return
 	}
 }
+*/
 
 /*
 func TestReadRAIDInfo(t *testing.T) {

+ 107 - 0
mod/disk/raid/raidutils.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"strings"
 )
 
@@ -83,3 +84,109 @@ func DeviceIsMounted(devicePath string) (bool, error) {
 	// Device is not mounted
 	return false, nil
 }
+
+// Use to restart any not-removed RAID device
+func (m *Manager) RestartRAIDService() error {
+	cmd := exec.Command("sudo", "mdadm", "--assemble", "--scan")
+
+	// Run the command
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		if string(output) == "" {
+			//Nothing updated in config.
+			return nil
+		}
+		return fmt.Errorf("error restarting RAID device: %s", strings.TrimSpace(string(output)))
+	}
+
+	return nil
+}
+
+// Stop RAID device with given path
+func (m *Manager) StopRAIDDevice(devicePath string) error {
+	cmd := exec.Command("sudo", "mdadm", "--stop", devicePath)
+
+	// Run the command
+	err := cmd.Run()
+	if err != nil {
+		return fmt.Errorf("error stopping RAID device: %v", err)
+	}
+
+	return nil
+}
+
+// RemoveRAIDDevice removes the specified RAID device member (disk).
+func (m *Manager) RemoveRAIDMember(devicePath string) error {
+	// Construct the mdadm command to remove the RAID device
+	cmd := exec.Command("sudo", "mdadm", "--remove", devicePath)
+
+	// Run the command
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		// If there was an error, return the combined output and the error message
+		return fmt.Errorf("error removing RAID device: %s", strings.TrimSpace(string(output)))
+	}
+
+	return nil
+}
+
+// UnmountDevice unmounts the specified device.
+// Remember to use full path (e.g. /dev/md0) in the devicePath
+func UnmountDevice(devicePath string) error {
+	// Construct the bash command to unmount the device
+	cmd := exec.Command("sudo", "bash", "-c", fmt.Sprintf("umount %s", devicePath))
+
+	// Run the command
+	err := cmd.Run()
+	if err != nil {
+		return fmt.Errorf("error unmounting device: %v", err)
+	}
+
+	return nil
+}
+
+// IsValidRAIDLevel checks if the given RAID level is valid.
+func IsValidRAIDLevel(level string) bool {
+	// List of valid RAID levels
+	validLevels := []string{"raid1", "raid0", "raid6", "raid5", "raid4", "raid10"}
+
+	// Convert the RAID level to lowercase and remove any surrounding whitespace
+	level = strings.TrimSpace(strings.ToLower(level))
+
+	// Check if the level exists in the list of valid levels
+	for _, validLevel := range validLevels {
+		if level == validLevel {
+			return true
+		}
+	}
+
+	// Return false if the level is not found in the list of valid levels
+	return false
+}
+
+// Get RAID device info from device path
+func (m *Manager) GetRAIDDeviceByDevicePath(devicePath string) (*RAIDDevice, error) {
+	//Strip the /dev/ part if it was accidentally passed in
+	devicePath = filepath.Base(devicePath)
+
+	//Get all the raid devices
+	rdevs, err := m.GetRAIDDevicesFromProcMDStat()
+	if err != nil {
+		return nil, err
+	}
+
+	//Check for match
+	for _, rdev := range rdevs {
+		if rdev.Name == devicePath {
+			return &rdev, nil
+		}
+	}
+
+	return nil, errors.New("target RAID device not found")
+}
+
+// Check if a RAID device exists, e.g. md0
+func (m *Manager) RAIDDeviceExists(devicePath string) bool {
+	_, err := m.GetRAIDDeviceByDevicePath(devicePath)
+	return err == nil
+}

+ 25 - 0
web/SystemAO/desktop/utils/diskoverview.html

@@ -31,6 +31,10 @@
             background-color: #b51d1d !important;
         }
 
+        #diskrender .progress .bar.raiddev{
+            background-color: #f5ef42 !important;
+        }
+
         #diskrender .progress .bar .progress{
             background-color: transparent !important;
         }
@@ -63,6 +67,20 @@
                             let thisDiskInfo = data[0]["blockdevices"][i];
                             let driveId = thisDiskInfo.name;
                             let diskName = "";
+                            let isRaid = false;
+
+                            if (thisDiskInfo.children == null){
+                                //This is a disk that is not formated or damaged
+                                continue;
+                            }
+
+                            console.log(data[1]["blockdevices"][i]);
+                            if (data[1]["blockdevices"][i].fstype == "linux_raid_member"){
+                                //RAID source partition / drive
+                                continue; //Remove this line to also render RAID disk
+                                isRaid = true;
+                            }
+
                             if (thisDiskInfo.children.length > 0){
                                 //Try to extract mountpoint as name
                                 let mountpoint = thisDiskInfo.children[0].mountpoint;
@@ -107,6 +125,13 @@
                                 usedPercentage = 100;
                             }
 
+                            //Check if raid. As raid is controlled by mdadm, we don't know how much storage it has
+                            if (isRaid){
+                                usedPercentage = 100;
+                                colorClass = "raiddev";
+                                diskName = "[RAID Disk]"
+                            }
+
                             $("#diskspaceList").append(`<div class="item driveinfo">
                                 <img class="ui avatar image" src="img/system/drive.svg">
                                 <div class="content">

+ 0 - 0
web/SystemAO/disk/raid/index.html