mdadmConf.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package raid
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "os/exec"
  7. "strings"
  8. "time"
  9. "imuslab.com/bokofs/bokofsd/mod/disktool/diskfs"
  10. "imuslab.com/bokofs/bokofsd/mod/utils"
  11. )
  12. /*
  13. mdadmConf.go
  14. This package handles the config modification and update for
  15. the mdadm module
  16. */
  17. // Force mdadm to stop all RAID and load fresh from config file
  18. // on some Linux distro this is required as mdadm start too early
  19. func (m *Manager) FlushReload() error {
  20. //Get a list of currently running RAID devices
  21. raidDevices, err := m.GetRAIDDevicesFromProcMDStat()
  22. if err != nil {
  23. return err
  24. }
  25. //Stop all of the running RAID devices
  26. for _, rd := range raidDevices {
  27. //Check if it is mounted. If yes, skip this
  28. devMounted, err := diskfs.DeviceIsMounted("/dev/" + rd.Name)
  29. if devMounted || err != nil {
  30. log.Println("[RAID] " + "/dev/" + rd.Name + " is in use. Skipping.")
  31. continue
  32. }
  33. log.Println("[RAID] Stopping " + rd.Name)
  34. cmdMdadm := exec.Command("sudo", "mdadm", "--stop", "/dev/"+rd.Name)
  35. // Run the command and capture its output
  36. _, err = cmdMdadm.Output()
  37. if err != nil {
  38. log.Println("[RAID] Unable to stop " + rd.Name + ". Skipping")
  39. continue
  40. }
  41. }
  42. time.Sleep(300 * time.Millisecond)
  43. //Assemble mdadm array again
  44. err = m.RestartRAIDService()
  45. if err != nil {
  46. return err
  47. }
  48. return nil
  49. }
  50. // removeDevicesEntry remove device hardcode from mdadm config file
  51. func removeDevicesEntry(configLine string) string {
  52. // Split the config line by space character
  53. tokens := strings.Fields(configLine)
  54. // Iterate through the tokens to find and remove the devices=* part
  55. for i, token := range tokens {
  56. if strings.HasPrefix(token, "devices=") {
  57. // Remove the devices=* part from the slice
  58. tokens = append(tokens[:i], tokens[i+1:]...)
  59. break
  60. }
  61. }
  62. // Join the tokens back into a single string
  63. updatedConfigLine := strings.Join(tokens, " ")
  64. return updatedConfigLine
  65. }
  66. // Updates the mdadm configuration file with the details of RAID arrays
  67. // so the RAID drive will still be seen after a reboot (hopefully)
  68. // this will automatically add / remove config base on current runtime setup
  69. func (m *Manager) UpdateMDADMConfig() error {
  70. cmdMdadm := exec.Command("sudo", "mdadm", "--detail", "--scan", "--verbose")
  71. // Run the command and capture its output
  72. output, err := cmdMdadm.Output()
  73. if err != nil {
  74. return fmt.Errorf("error running mdadm command: %v", err)
  75. }
  76. //Load the config from system
  77. currentConfigBytes, err := os.ReadFile("/etc/mdadm/mdadm.conf")
  78. if err != nil {
  79. return fmt.Errorf("unable to open mdadm.conf: " + err.Error())
  80. }
  81. currentConf := string(currentConfigBytes)
  82. //Check if the current config already contains the setting
  83. newConfigLines := []string{}
  84. uuidsInNewConfig := []string{}
  85. arrayConfigs := strings.TrimSpace(string(output))
  86. lines := strings.Split(arrayConfigs, "ARRAY")
  87. for _, line := range lines {
  88. //For each line, you should have something like this
  89. //ARRAY /dev/md0 metadata=1.2 name=debian:0 UUID=cbc11a2b:fbd42653:99c1340b:9c4962fb
  90. // devices=/dev/sdb,/dev/sdc
  91. //Building structure for RAID Config Record
  92. line = strings.ReplaceAll(line, "\n", " ")
  93. fields := strings.Fields(line)
  94. if len(fields) < 5 {
  95. continue
  96. }
  97. poolUUID := strings.TrimPrefix(fields[3], "UUID=")
  98. uuidsInNewConfig = append(uuidsInNewConfig, poolUUID)
  99. //Check if this uuid already in the config file
  100. if strings.Contains(currentConf, poolUUID) {
  101. continue
  102. }
  103. //This config not exists in the settings. Add it to append lines
  104. log.Println("[RAID] Adding " + fields[0] + " (UUID=" + poolUUID + ") into mdadm config")
  105. settingLine := "ARRAY " + strings.Join(fields, " ")
  106. //Remove the device specific names
  107. settingLine = removeDevicesEntry(settingLine)
  108. newConfigLines = append(newConfigLines, settingLine)
  109. }
  110. originalConfigLines := strings.Split(strings.TrimSpace(currentConf), "\n")
  111. poolUUIDToBeRemoved := []string{}
  112. for _, line := range originalConfigLines {
  113. lineFields := strings.Fields(line)
  114. for _, thisField := range lineFields {
  115. if strings.HasPrefix(thisField, "UUID=") {
  116. //This is the UUID of this array. Check if it still exists in new storage config
  117. thisPoolUUID := strings.TrimPrefix(thisField, "UUID=")
  118. existsInNewConfig := utils.StringInArray(uuidsInNewConfig, thisPoolUUID)
  119. if !existsInNewConfig {
  120. //Label this UUID to be removed
  121. poolUUIDToBeRemoved = append(poolUUIDToBeRemoved, thisPoolUUID)
  122. }
  123. //Skip scanning the remaining fields of this RAID pool
  124. break
  125. }
  126. }
  127. }
  128. if len(poolUUIDToBeRemoved) > 0 {
  129. //Remove the old UUIDs from config
  130. for _, volumeUUID := range poolUUIDToBeRemoved {
  131. err = m.RemoveVolumeFromMDADMConfig(volumeUUID)
  132. if err != nil {
  133. log.Println("[RAID] Error when trying to remove old RAID volume from config: " + err.Error())
  134. return err
  135. } else {
  136. log.Println("[RAID] RAID volume " + volumeUUID + " removed from config file")
  137. }
  138. }
  139. }
  140. if len(newConfigLines) == 0 {
  141. //Nothing to write
  142. log.Println("[RAID] Nothing to write. Skipping mdadm config update.")
  143. return nil
  144. }
  145. // Construct the bash command to append the line to mdadm.conf using echo and tee
  146. for _, configLine := range newConfigLines {
  147. cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "%s" | sudo tee -a /etc/mdadm/mdadm.conf`, configLine))
  148. // Run the command
  149. err := cmd.Run()
  150. if err != nil {
  151. return fmt.Errorf("error injecting line into mdadm.conf: %v", err)
  152. }
  153. }
  154. return nil
  155. }
  156. // Removes a RAID volume from the mdadm configuration file given its volume UUID.
  157. // Note that this only remove a single line of config. If your line consists of multiple lines
  158. // you might need to remove it manually
  159. func (m *Manager) RemoveVolumeFromMDADMConfig(volumeUUID string) error {
  160. // Construct the sed command to remove the line containing the volume UUID from mdadm.conf
  161. sedCommand := fmt.Sprintf(`sudo sed -i '/UUID=%s/d' /etc/mdadm/mdadm.conf`, volumeUUID)
  162. // Execute the sed command
  163. cmd := exec.Command("bash", "-c", sedCommand)
  164. err := cmd.Run()
  165. if err != nil {
  166. return fmt.Errorf("error removing volume from mdadm.conf: %v", err)
  167. }
  168. return nil
  169. }