Forráskód Böngészése

Optimized raid0 support and detection for min disk

aroz 1 éve
szülő
commit
fcc782561e

+ 3 - 1
disk.go

@@ -27,7 +27,9 @@ func RAIDServiceInit() {
 	*/
 
 	if *allow_hardware_management {
-		rm, err := raid.NewRaidManager(raid.Options{})
+		rm, err := raid.NewRaidManager(raid.Options{
+			Logger: systemWideLogger,
+		})
 		if err == nil {
 			raidManager = rm
 

+ 64 - 14
mod/disk/raid/handler.go

@@ -2,7 +2,6 @@ package raid
 
 import (
 	"encoding/json"
-	"fmt"
 	"log"
 	"net/http"
 	"path/filepath"
@@ -332,8 +331,7 @@ func (m *Manager) HandleListRaidDevices(w http.ResponseWriter, r *http.Request)
 
 // Create a RAID storage pool
 func (m *Manager) HandleCreateRAIDDevice(w http.ResponseWriter, r *http.Request) {
-	//TODO: Change GetPara to Post
-	devName, err := utils.GetPara(r, "devName")
+	devName, err := utils.PostPara(r, "devName")
 	if err != nil || devName == "" {
 		//Use auto generated one
 		devName, err = GetNextAvailableMDDevice()
@@ -342,29 +340,35 @@ func (m *Manager) HandleCreateRAIDDevice(w http.ResponseWriter, r *http.Request)
 			return
 		}
 	}
-	raidName, err := utils.GetPara(r, "raidName")
+	raidName, err := utils.PostPara(r, "raidName")
 	if err != nil {
 		utils.SendErrorResponse(w, "invalid raid storage name given")
 		return
 	}
-	raidLevelStr, err := utils.GetPara(r, "level")
+	raidLevelStr, err := utils.PostPara(r, "level")
 	if err != nil {
 		utils.SendErrorResponse(w, "invalid raid level given")
 		return
 	}
 
-	raidDevicesJSON, err := utils.GetPara(r, "raidDev")
+	raidDevicesJSON, err := utils.PostPara(r, "raidDev")
 	if err != nil {
 		utils.SendErrorResponse(w, "invalid raid device array given")
 		return
 	}
 
-	spareDevicesJSON, err := utils.GetPara(r, "spareDev")
+	spareDevicesJSON, err := utils.PostPara(r, "spareDev")
 	if err != nil {
 		utils.SendErrorResponse(w, "invalid spare device array given")
 		return
 	}
 
+	//Get if superblock require all zeroed (will also do formating after raid constructed)
+	zerosuperblock, err := utils.PostBool(r, "zerosuperblock")
+	if err != nil {
+		zerosuperblock = false
+	}
+
 	//Convert raidDevices and spareDevices ID into string slice
 	raidDevices := []string{}
 	spareDevices := []string{}
@@ -388,12 +392,60 @@ func (m *Manager) HandleCreateRAIDDevice(w http.ResponseWriter, r *http.Request)
 	}
 
 	//Convert raidLevel to int
+	raidLevelStr = strings.TrimPrefix(raidLevelStr, "raid")
 	raidLevel, err := strconv.Atoi(raidLevelStr)
 	if err != nil {
 		utils.SendErrorResponse(w, "invalid raid level given")
 		return
 	}
 
+	if zerosuperblock {
+		//Format each drives
+		drivesToZeroblocks := []string{}
+		for _, raidDev := range raidDevices {
+			if !strings.HasPrefix(raidDev, "/dev/") {
+				//Prepend /dev/ to it if not set
+				raidDev = filepath.Join("/dev/", raidDev)
+			}
+
+			if !utils.FileExists(raidDev) {
+				//This disk not found
+				utils.SendErrorResponse(w, raidDev+" not found")
+				return
+			}
+
+			thisDisk := raidDev
+			drivesToZeroblocks = append(drivesToZeroblocks, thisDisk)
+		}
+
+		for _, spareDev := range spareDevices {
+			if !strings.HasPrefix(spareDev, "/dev/") {
+				//Prepend /dev/ to it if not set
+				spareDev = filepath.Join("/dev/", spareDev)
+			}
+
+			if !utils.FileExists(spareDev) {
+				//This disk not found
+				utils.SendErrorResponse(w, spareDev+" not found")
+				return
+			}
+
+			thisDisk := spareDev
+			drivesToZeroblocks = append(drivesToZeroblocks, thisDisk)
+		}
+
+		for _, clearPendingDisk := range drivesToZeroblocks {
+			//Format all drives
+			m.Options.Logger.PrintAndLog("RAID", "Clearning superblock for disk "+clearPendingDisk, nil)
+			err = m.ClearSuperblock(clearPendingDisk)
+			if err != nil {
+				m.Options.Logger.PrintAndLog("RAID", "Unable to format "+clearPendingDisk+": "+err.Error(), err)
+				utils.SendErrorResponse(w, err.Error())
+				return
+			}
+		}
+	}
+
 	//Create the RAID device
 	err = m.CreateRAIDDevice(devName, raidName, raidLevel, raidDevices, spareDevices)
 	if err != nil {
@@ -451,15 +503,13 @@ func (m *Manager) HandleRemoveRaideDevice(w http.ResponseWriter, r *http.Request
 
 	mounted, err := diskfs.DeviceIsMounted(targetDevice)
 	if err != nil {
-		log.Println("[RAID] Unmount failed: " + err.Error())
+		m.Options.Logger.PrintAndLog("RAID", "Unmount failed: "+err.Error(), err)
 		utils.SendErrorResponse(w, err.Error())
 		return
 	}
 
-	fmt.Println(mounted)
-
 	if mounted {
-		log.Println("[RAID] " + targetDevice + " is mounted. Trying to unmount...")
+		m.Options.Logger.PrintAndLog("RAID", targetDevice+" is mounted. Trying to unmount...", nil)
 		err = diskfs.UnmountDevice(targetDevice)
 		if err != nil {
 			log.Println("[RAID] Unmount failed: " + err.Error())
@@ -473,7 +523,7 @@ func (m *Manager) HandleRemoveRaideDevice(w http.ResponseWriter, r *http.Request
 			mounted, _ := diskfs.DeviceIsMounted(targetDevice)
 			if mounted {
 				//Still not unmounted. Wait for it
-				log.Println("[RAID] Device still mounted. Retrying in 1 second")
+				m.Options.Logger.PrintAndLog("RAID", "Device still mounted. Retrying in 1 second", nil)
 				counter++
 				time.Sleep(1 * time.Second)
 			} else {
@@ -495,7 +545,7 @@ func (m *Manager) HandleRemoveRaideDevice(w http.ResponseWriter, r *http.Request
 	//Stop & Remove RAID service on the target device
 	err = m.StopRAIDDevice(targetDevice)
 	if err != nil {
-		log.Println("[RAID] Stop RAID partition failed: " + err.Error())
+		m.Options.Logger.PrintAndLog("RAID", "Stop RAID partition failed: "+err.Error(), err)
 		utils.SendErrorResponse(w, err.Error())
 		return
 	}
@@ -510,7 +560,7 @@ func (m *Manager) HandleRemoveRaideDevice(w http.ResponseWriter, r *http.Request
 
 		err = m.ClearSuperblock(name)
 		if err != nil {
-			log.Println("[RAID] Unable to clear superblock on device " + name)
+			m.Options.Logger.PrintAndLog("RAID", "Unable to clear superblock on device "+name, err)
 			continue
 		}
 	}

+ 18 - 2
mod/disk/raid/mdadm.go

@@ -88,12 +88,28 @@ func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel in
 		return errors.New(devName + " already been used")
 	}
 
+	//Append /dev to the name of the raid device ids and spare device ids if missing
+	for i, raidDev := range raidDeviceIds {
+		if !strings.HasPrefix(raidDev, "/dev/") {
+			raidDeviceIds[i] = filepath.Join("/dev/", raidDev)
+		}
+	}
+	for i, spareDev := range spareDeviceIds {
+		if !strings.HasPrefix(spareDev, "/dev/") {
+			spareDeviceIds[i] = filepath.Join("/dev/", spareDev)
+		}
+	}
+
 	// Concatenate RAID and spare device arrays
 	allDeviceIds := append(raidDeviceIds, spareDeviceIds...)
 
 	// Build the mdadm command
-	cmd := exec.Command("bash", "-c", fmt.Sprintf("yes | sudo mdadm --create %s --name %s --level=%d --raid-devices=%d --spare-devices=%d %s",
-		devName, raidName, raidLevel, raidDev, spareDevice, strings.Join(allDeviceIds, " ")))
+	mdadmCommand := fmt.Sprintf("yes | sudo mdadm --create %s --name %s --level=%d --raid-devices=%d --spare-devices=%d %s", devName, raidName, raidLevel, raidDev, spareDevice, strings.Join(allDeviceIds, " "))
+	if raidLevel == 0 {
+		//raid0 cannot use --spare-device command as there is no failover
+		mdadmCommand = fmt.Sprintf("yes | sudo mdadm --create %s --name %s --level=%d --raid-devices=%d %s", devName, raidName, raidLevel, raidDev, strings.Join(allDeviceIds, " "))
+	}
+	cmd := exec.Command("bash", "-c", mdadmCommand)
 
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr

+ 2 - 0
mod/disk/raid/raid.go

@@ -9,6 +9,7 @@ import (
 	"runtime"
 
 	"imuslab.com/arozos/mod/apt"
+	"imuslab.com/arozos/mod/info/logger"
 	"imuslab.com/arozos/mod/utils"
 )
 
@@ -18,6 +19,7 @@ import (
 */
 
 type Options struct {
+	Logger *logger.Logger
 }
 
 type Manager struct {

+ 34 - 0
mod/utils/utils.go

@@ -8,6 +8,7 @@ import (
 	"log"
 	"net/http"
 	"os"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -61,6 +62,39 @@ func PostPara(r *http.Request, key string) (string, error) {
 	}
 }
 
+func PostBool(r *http.Request, key string) (bool, error) {
+	x, err := PostPara(r, key)
+	if err != nil {
+		return false, err
+	}
+
+	x = strings.TrimSpace(x)
+
+	if x == "1" || strings.ToLower(x) == "true" {
+		return true, nil
+	} else if x == "0" || strings.ToLower(x) == "false" {
+		return false, nil
+	}
+
+	return false, errors.New("invalid boolean given")
+}
+
+// Get POST paramter as int
+func PostInt(r *http.Request, key string) (int, error) {
+	x, err := PostPara(r, key)
+	if err != nil {
+		return 0, err
+	}
+
+	x = strings.TrimSpace(x)
+	rx, err := strconv.Atoi(x)
+	if err != nil {
+		return 0, err
+	}
+
+	return rx, nil
+}
+
 func FileExists(filename string) bool {
 	_, err := os.Stat(filename)
 	if os.IsNotExist(err) {

+ 7 - 3
web/SystemAO/disk/raid/index.html

@@ -243,8 +243,8 @@
                         <div id="raidDiskList">
                             
                         </div>
-                        <div class="ui yellow message" style="display:none;" id="raidDataLossWarning">
-                            <i class="yellow exclamation triangle icon"></i> Not enough redundant disks. Further removal / failure of disk will result in data loss.<br>
+                        <div class="ui message" style="display:none;" id="raidDataLossWarning">
+                            <i class="yellow exclamation triangle icon"></i> Minimum disk number reached. "Remove Disk" function is disabled to prevent data loss.<br>
                         </div>
                         <div class="ui divider"></div>
                         <div style="width: 100%; text-align:  center; margin-bottom: 1em;">
@@ -353,7 +353,10 @@
             }
 
             window.handleRAIDCreateCallback = function(data){
-
+                //Done adding disk
+                setTimeout(function(){
+                    reloadRAIDVolDetail();
+                }, 300);
             }
 
             /* RAID Remove Function */
@@ -534,6 +537,7 @@
 
             //Reload the current raid volume detail
             function reloadRAIDVolDetail(){
+                $("#raidDataLossWarning").hide();
                 if (raidManager.editingArray != undefined){
                     initRAIDVolList(raidManager.editingArray);
                 } 

+ 90 - 7
web/SystemAO/disk/raid/newRAID.html

@@ -134,8 +134,8 @@
                             <div class="menu">
                             <div class="item" data-value="raid1">RAID 1 (Mirror, Recommend)</div>
                             <div class="item" data-value="raid5">RAID 5 (Lose 1 Disk Max.)</div>
-                            <div class="item" data-value="raid6">RAID 6 (Lose 2 Disk Max.)</div>
-                            <div class="item" data-value="raid6">RAID 10</div>
+                            <!-- <div class="item" data-value="raid6">RAID 6 (Lose 2 Disk Max.)</div> -->
+                            <!-- <div class="item" data-value="raid6">RAID 10</div> -->
                             <div class="item" data-value="raid0">RAID 0 (Striped, Not Recommend)</div>
                             </div>
                         </div>
@@ -159,6 +159,9 @@
                     </div>
                     <p>Match data redundancy requirement disk count: <span id="matchRedundancy">n/a</span></p>
                 </div>
+                <div id="notEnoughDiskErrorMessage" class="ui red message" style="display:none;">
+                    <i class="ui red circle times icon"></i> Not enough disk selected for <span id="errorRaidType"></span>
+                </div>
                 <br>
                 <div class="ui basic right floated button" onclick="createRAID(event);">
                     <i class="ui icons">
@@ -184,7 +187,7 @@
               <div class="description">
                 <h3 id="oprconfirm"></h3>
                 <p><b>Selecting these disk will erase all data current on the disk.</b> Confirm?</p>
-                
+                <p id="erasePendingDisk"></p>
                 <div class="advanceinfo">
                     <small >
                         <b>Notes on "Continue Without Format"</b><br>
@@ -199,8 +202,8 @@
                 <i class="check icon"></i>
                 Confirm
               </div>
-              <div class="ui orange button" onclick="confirmAddDiskWithoutFormat();">
-                Continue without Format
+              <div class="ui yellow button" onclick="confirmAddDiskWithoutFormat();">
+                <i class="ui exclamation triangle icon"></i> Continue without Format
               </div>
               <div class="ui basic deny button">
                 Cancel
@@ -211,6 +214,7 @@
         <br><br>
         <script>
             let diskInfo = {};
+            let erasePendingDisks = []; //List of disk prepare to be formatted and create an RAID array
             $(".dropdown").dropdown();
 
             function bytesToSize(bytes) {
@@ -289,7 +293,7 @@
                             }
                             
                             var domUID = uuidv4();
-                            let diskDOM = (`<div onclick="handleSelect(this);" dsize="${driveInfo.size}" class="ui segment installedDisk ${domUID} ${notUsable?"disabled":""}">
+                            let diskDOM = (`<div onclick="handleSelect(this);" dname="${driveInfo.name}" dsize="${driveInfo.size}" class="ui segment installedDisk ${domUID} ${notUsable?"disabled":""}">
                                 <h4 class="ui header">
                                     <img src="./img/drive.svg">
                                     <div class="content">
@@ -512,15 +516,94 @@
                     return
                 }
 
+                //Check if the raid selection is valid
+                let selectedDiskCount = $(".installedDisk.active").length;
+                let raidLevel = $("#raidtype")[0].value;
+                if (!(checkDiskNumber(selectedDiskCount, raidLevel))){
+                    $("#errorRaidType").text(raidLevel);
+                    $("#notEnoughDiskErrorMessage").slideDown();
+                    return
+                }else{
+                    $("#notEnoughDiskErrorMessage").slideUp();
+                }
+
+                //Generate a list of disk to erase
+                erasePendingDisks = [];
+                $(".installedDisk.active").each(function(){
+                    let diskName = $(this).find(".diskname").text();
+                    let diskID = $(this).attr("dname");
+                    let diskSize = $(this).attr("dsize");
+                    erasePendingDisks.push([diskID, diskName, parseInt(diskSize)]);
+                });
+
+                let earseDiskList = [];
+                erasePendingDisks.forEach(diskToBeErased => {
+                    earseDiskList.push(`/dev/${diskToBeErased[0]} (${diskToBeErased[1]} - ${bytesToSize(diskToBeErased[2])})`);
+                });
+                $("#erasePendingDisk").html(earseDiskList.join("<br>"));
+               
 
                 $("#volumeName").parent().removeClass("error");
                 $("#confirmDiskChoice").modal("show");
             }
 
+            //Confirm create an arary. 
             function confirmAddDisk(){
+                createRAIDArray(true);
+            }
+
+            function confirmAddDiskWithoutFormat(){
+                createRAIDArray(false);
+            }
+
+            function createRAIDArray(preformat=true){
                 let volumeName = $("#volumeName").val().trim();
                 let raidLevel = $("#raidtype")[0].value;
-                
+
+                //Generate the correct struct required by API endpoint to create raid
+                let raidDev = [];
+                erasePendingDisks.forEach(function(diskInfo){
+                    raidDev.push(diskInfo[0]); //device path, e.g. /dev/sdb
+                });
+
+                //Reserved for future if needed
+                let spareDev = [];
+
+                $.ajax({
+                    url: "../../../system/disk/raid/new",
+                    method: "POST",
+                    data: {
+                        "raidName": volumeName,
+                        "level": raidLevel,
+                        "raidDev": JSON.stringify(raidDev),
+                        "spareDev": JSON.stringify(spareDev),
+                        "zerosuperblock": preformat
+                    },
+                    success: function(data){
+                        if (data.error != undefined){
+                            alert(data.error);
+                        }else{
+                            //Done
+                            exitToParentWindow(data);
+                        }
+
+                    },
+                    error: function(){
+                        alert("Error occured when requesting create RAID endpoint");
+                    }
+                })
+            }
+
+            function exitToParentWindow(callbackdata){
+                //Disk added. Handle callback to parent 
+                if (ao_module_hasParentCallback()){
+                    ao_module_parentCallback(callbackdata);
+                }
+
+                //Operation completed. 
+                setTimeout(function(){
+                    ao_module_close();
+                }, 300);
             }
         </script>
     </body>