mdadm.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package raid
  2. import (
  3. "fmt"
  4. "os"
  5. "os/exec"
  6. "strconv"
  7. "strings"
  8. )
  9. /*
  10. mdadm manager
  11. This script handles the interaction with mdadm
  12. */
  13. type StorageDeviceMeta struct {
  14. Name string
  15. Size int64
  16. RO bool
  17. DevType string
  18. MountPoint string
  19. }
  20. // List all the storage device in the system, set minSize to 0 for no filter
  21. func (m *Manager) ListAllStorageDevices(minSize int64) ([]*StorageDeviceMeta, error) {
  22. cmd := exec.Command("sudo", "lsblk", "-b")
  23. output, err := cmd.CombinedOutput()
  24. if err != nil {
  25. return nil, fmt.Errorf("lsblk error: %v", err)
  26. }
  27. // Split the output into lines
  28. lines := strings.Split(string(output), "\n")
  29. var devices []*StorageDeviceMeta
  30. // Parse each line to extract device information
  31. for _, line := range lines[1:] { // Skip the header line
  32. fields := strings.Fields(line)
  33. if len(fields) < 7 {
  34. continue
  35. }
  36. size, err := strconv.ParseInt(fields[3], 10, 64)
  37. if err != nil {
  38. return nil, fmt.Errorf("error parsing device size: %v", err)
  39. }
  40. ro := fields[4] == "1"
  41. device := &StorageDeviceMeta{
  42. Name: fields[0],
  43. Size: size,
  44. RO: ro,
  45. DevType: fields[5],
  46. MountPoint: fields[6],
  47. }
  48. // Filter devices based on minimum size
  49. if size >= minSize {
  50. devices = append(devices, device)
  51. }
  52. }
  53. return devices, nil
  54. }
  55. // Return the uuid of the disk by its path name (e.g. /dev/sda)
  56. func (m *Manager) GetDiskUUIDByPath(devicePath string) (string, error) {
  57. cmd := exec.Command("sudo", "blkid", devicePath)
  58. output, err := cmd.CombinedOutput()
  59. if err != nil {
  60. return "", fmt.Errorf("blkid error: %v", err)
  61. }
  62. // Parse the output to extract the UUID
  63. fields := strings.Fields(string(output))
  64. for _, field := range fields {
  65. if strings.HasPrefix(field, "UUID=") {
  66. uuid := strings.TrimPrefix(field, "UUID=\"")
  67. uuid = strings.TrimSuffix(uuid, "\"")
  68. return uuid, nil
  69. }
  70. }
  71. return "", fmt.Errorf("UUID not found for device %s", devicePath)
  72. }
  73. // CreateRAIDDevice creates a RAID device using the mdadm command.
  74. func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel int, raidDeviceIds []string, spareDeviceIds []string) error {
  75. //Calculate the size of the raid devices
  76. raidDev := len(raidDeviceIds)
  77. spareDevice := len(spareDeviceIds)
  78. //Validate the number of disk is enough for the raid
  79. if raidLevel == 0 && raidDev < 2 {
  80. return fmt.Errorf("not enough disks for raid0")
  81. } else if raidLevel == 1 && raidDev < 2 {
  82. return fmt.Errorf("not enough disks for raid1")
  83. } else if raidLevel == 5 && raidDev < 3 {
  84. return fmt.Errorf("not enough disk for raid5")
  85. } else if raidLevel == 6 && raidDev < 4 {
  86. return fmt.Errorf("not enough disk for raid6")
  87. }
  88. // Concatenate RAID and spare device arrays
  89. allDeviceIds := append(raidDeviceIds, spareDeviceIds...)
  90. // Build the mdadm command
  91. cmd := exec.Command("bash", "-c", fmt.Sprintf("yes | sudo mdadm --create %s --name %s --level=%d --raid-devices=%d --spare-devices=%d %s",
  92. devName, raidName, raidLevel, raidDev, spareDevice, strings.Join(allDeviceIds, " ")))
  93. cmd.Stdout = os.Stdout
  94. cmd.Stderr = os.Stderr
  95. err := cmd.Run()
  96. if err != nil {
  97. return fmt.Errorf("error running mdadm command: %v", err)
  98. }
  99. return nil
  100. }
  101. // Updates the mdadm configuration file with the details of RAID arrays
  102. // so the RAID drive will still be seen after a reboot (hopefully)
  103. func UpdateMDADMConfig() error {
  104. cmdMdadm := exec.Command("sudo", "mdadm", "--detail", "--scan", "--verbose")
  105. // Run the command and capture its output
  106. output, err := cmdMdadm.Output()
  107. if err != nil {
  108. return fmt.Errorf("error running mdadm command: %v", err)
  109. }
  110. //Load the config from system
  111. currentConfigBytes, err := os.ReadFile("/etc/mdadm/mdadm.conf")
  112. if err != nil {
  113. return fmt.Errorf("unable to open mdadm.conf: " + err.Error())
  114. }
  115. currentConf := string(currentConfigBytes)
  116. //Check if the current config already contains the setting
  117. newConfigLines := []string{}
  118. arrayConfigs := strings.TrimSpace(string(output))
  119. lines := strings.Split(arrayConfigs, "ARRAY")
  120. for _, line := range lines {
  121. //For each line, you should have something like this
  122. //ARRAY /dev/md0 metadata=1.2 name=debian:0 UUID=cbc11a2b:fbd42653:99c1340b:9c4962fb
  123. // devices=/dev/sdb,/dev/sdc
  124. line = strings.ReplaceAll(line, "\n", " ")
  125. fields := strings.Fields(line)
  126. poolUUID := strings.TrimPrefix(fields[3], "UUID=")
  127. //Check if this uuid already in the config file
  128. if strings.Contains(currentConf, poolUUID) {
  129. continue
  130. }
  131. //This config not exists in the settings. Add it to append lines
  132. settingLine := "ARRAY " + strings.Join(fields, " ")
  133. newConfigLines = append(newConfigLines, settingLine)
  134. }
  135. fmt.Println(newConfigLines)
  136. //TODO: APPEND THE LINES INTO THE CONFIG FILE
  137. return nil
  138. }