reconcile.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package storage
  2. /*
  3. Reconcile: the real files on this node's volumes are authoritative. A
  4. rescan adopts files that appeared in a contributed folder, restores a
  5. metadata store that was rebuilt, and downgrades copies that changed on
  6. disk to stale. It never deletes real files.
  7. */
  8. import (
  9. "io/fs"
  10. "os"
  11. "path/filepath"
  12. "strings"
  13. "time"
  14. uuid "github.com/satori/go.uuid"
  15. "imuslab.com/arozos/mod/cluster/metadata"
  16. "imuslab.com/arozos/mod/info/logger"
  17. )
  18. // ReconcileResult summarises one rescan.
  19. type ReconcileResult struct {
  20. VolumeID string `json:"volumeId"`
  21. Adopted int `json:"adopted"` // files that had no record
  22. Attached int `json:"attached"` // records that gained this volume as a copy
  23. Stale int `json:"stale"` // copies that no longer match or are missing
  24. Scanned int `json:"scanned"`
  25. Error string `json:"error,omitempty"`
  26. Time int64 `json:"time"`
  27. }
  28. // ReconcileAll rescans every local volume.
  29. func (s *Service) ReconcileAll() []ReconcileResult {
  30. out := []ReconcileResult{}
  31. if !s.Ready() {
  32. return out
  33. }
  34. for _, v := range s.LocalVolumes() {
  35. out = append(out, s.reconcileVolume(v))
  36. }
  37. return out
  38. }
  39. func (s *Service) reconcileVolume(vol metadata.Volume) ReconcileResult {
  40. res := ReconcileResult{VolumeID: vol.ID, Time: time.Now().Unix()}
  41. root, err := s.volumeRoot(&vol)
  42. if err != nil {
  43. res.Error = err.Error()
  44. return res
  45. }
  46. seen := map[string]bool{}
  47. cutoff := time.Now().Add(-SessionTTL)
  48. me := s.m.NodeID()
  49. walkErr := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
  50. if err != nil {
  51. return nil
  52. }
  53. if p == root {
  54. return nil
  55. }
  56. name := d.Name()
  57. if strings.Contains(name, ".part-") {
  58. if fi, err := d.Info(); err == nil && fi.ModTime().Before(cutoff) {
  59. os.Remove(p)
  60. }
  61. return nil
  62. }
  63. rel, err := filepath.Rel(root, p)
  64. if err != nil {
  65. return nil
  66. }
  67. logical := metadata.NormalizePath(filepath.ToSlash(rel))
  68. if d.IsDir() {
  69. if _, err := s.meta.Stat(logical); err != nil {
  70. s.ensureDirs(logical, "")
  71. }
  72. return nil
  73. }
  74. res.Scanned++
  75. seen[logical] = true
  76. fi, err := d.Info()
  77. if err != nil {
  78. return nil
  79. }
  80. rec, err := s.meta.Stat(logical)
  81. if err != nil {
  82. //No record: adopt the file
  83. sum, size, err := fileChecksum(p)
  84. if err != nil {
  85. return nil
  86. }
  87. if err := s.ensureDirs(metadata.ParentPath(logical), ""); err != nil {
  88. return nil
  89. }
  90. newRec := &metadata.FileRecord{
  91. ID: uuid.NewV4().String(),
  92. Path: logical,
  93. Size: size,
  94. ModTime: fi.ModTime().Unix(),
  95. Checksum: sum,
  96. Primary: vol.ID,
  97. }
  98. newRec.SetLocation(metadata.Location{VolumeID: vol.ID, NodeID: me, State: metadata.LocCommitted, Checksum: sum})
  99. if s.meta.Submit(metadata.KindFile, newRec) == nil {
  100. res.Adopted++
  101. }
  102. return nil
  103. }
  104. if rec.IsDir {
  105. return nil //a file where the namespace has a directory: leave it alone
  106. }
  107. loc, hasLoc := rec.Location(vol.ID)
  108. if hasLoc && loc.Healthy() && fi.Size() == rec.Size {
  109. return nil //fast path: size matches, trust it (nightly verify re-checks)
  110. }
  111. sum, size, err := fileChecksum(p)
  112. if err != nil {
  113. return nil
  114. }
  115. if sum == rec.Checksum && size == rec.Size {
  116. rec.SetLocation(metadata.Location{VolumeID: vol.ID, NodeID: me, State: metadata.LocCommitted, Checksum: sum})
  117. if rec.Primary == "" {
  118. rec.Primary = vol.ID
  119. }
  120. if s.meta.Submit(metadata.KindFile, rec) == nil {
  121. res.Attached++
  122. }
  123. return nil
  124. }
  125. //Content differs from the record
  126. if len(rec.HealthyLocations()) == 0 || (hasLoc && loc.NodeID == me && fi.ModTime().Unix() > rec.ModTime) {
  127. //No good copy anywhere (or this copy is newer): the disk wins
  128. rec.Size = size
  129. rec.Checksum = sum
  130. rec.ModTime = fi.ModTime().Unix()
  131. rec.Primary = vol.ID
  132. rec.Locations = nil
  133. rec.SetLocation(metadata.Location{VolumeID: vol.ID, NodeID: me, State: metadata.LocCommitted, Checksum: sum})
  134. s.meta.Submit(metadata.KindFile, rec)
  135. res.Attached++
  136. return nil
  137. }
  138. rec.SetLocation(metadata.Location{VolumeID: vol.ID, NodeID: me, State: metadata.LocStale, Checksum: sum})
  139. if s.meta.Submit(metadata.KindFile, rec) == nil {
  140. res.Stale++
  141. }
  142. return nil
  143. })
  144. if walkErr != nil {
  145. res.Error = walkErr.Error()
  146. }
  147. //Copies the record claims on this volume that are missing on disk
  148. for _, rec := range s.meta.AllFiles() {
  149. if rec.Removed || rec.IsDir || seen[rec.Path] {
  150. continue
  151. }
  152. loc, ok := rec.Location(vol.ID)
  153. if !ok || loc.State == metadata.LocStale || loc.State == metadata.LocWriting || loc.State == metadata.LocPending {
  154. continue
  155. }
  156. r := rec.Clone()
  157. loc.State = metadata.LocStale
  158. r.SetLocation(loc)
  159. if s.meta.Submit(metadata.KindFile, r) == nil {
  160. res.Stale++
  161. }
  162. }
  163. if res.Adopted+res.Attached+res.Stale > 0 {
  164. logger.PrintAndLog("Cluster", "Volume "+vol.Name+" rescanned: "+itoa(res.Adopted)+" adopted, "+itoa(res.Attached)+" attached, "+itoa(res.Stale)+" stale", nil)
  165. }
  166. return res
  167. }
  168. func itoa(v int) string {
  169. if v == 0 {
  170. return "0"
  171. }
  172. neg := v < 0
  173. if neg {
  174. v = -v
  175. }
  176. buf := [20]byte{}
  177. i := len(buf)
  178. for v > 0 {
  179. i--
  180. buf[i] = byte('0' + v%10)
  181. v /= 10
  182. }
  183. if neg {
  184. i--
  185. buf[i] = '-'
  186. }
  187. return string(buf[i:])
  188. }