| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- package storage
- /*
- Reconcile: the real files on this node's volumes are authoritative. A
- rescan adopts files that appeared in a contributed folder, restores a
- metadata store that was rebuilt, and downgrades copies that changed on
- disk to stale. It never deletes real files.
- */
- import (
- "io/fs"
- "os"
- "path/filepath"
- "strings"
- "time"
- uuid "github.com/satori/go.uuid"
- "imuslab.com/arozos/mod/cluster/metadata"
- "imuslab.com/arozos/mod/info/logger"
- )
- // ReconcileResult summarises one rescan.
- type ReconcileResult struct {
- VolumeID string `json:"volumeId"`
- Adopted int `json:"adopted"` // files that had no record
- Attached int `json:"attached"` // records that gained this volume as a copy
- Stale int `json:"stale"` // copies that no longer match or are missing
- Scanned int `json:"scanned"`
- Error string `json:"error,omitempty"`
- Time int64 `json:"time"`
- }
- // ReconcileAll rescans every local volume.
- func (s *Service) ReconcileAll() []ReconcileResult {
- out := []ReconcileResult{}
- if !s.Ready() {
- return out
- }
- for _, v := range s.LocalVolumes() {
- out = append(out, s.reconcileVolume(v))
- }
- return out
- }
- func (s *Service) reconcileVolume(vol metadata.Volume) ReconcileResult {
- res := ReconcileResult{VolumeID: vol.ID, Time: time.Now().Unix()}
- root, err := s.volumeRoot(&vol)
- if err != nil {
- res.Error = err.Error()
- return res
- }
- seen := map[string]bool{}
- cutoff := time.Now().Add(-SessionTTL)
- me := s.m.NodeID()
- walkErr := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
- if err != nil {
- return nil
- }
- if p == root {
- return nil
- }
- name := d.Name()
- if strings.Contains(name, ".part-") {
- if fi, err := d.Info(); err == nil && fi.ModTime().Before(cutoff) {
- os.Remove(p)
- }
- return nil
- }
- rel, err := filepath.Rel(root, p)
- if err != nil {
- return nil
- }
- logical := metadata.NormalizePath(filepath.ToSlash(rel))
- if d.IsDir() {
- if _, err := s.meta.Stat(logical); err != nil {
- s.ensureDirs(logical, "")
- }
- return nil
- }
- res.Scanned++
- seen[logical] = true
- fi, err := d.Info()
- if err != nil {
- return nil
- }
- rec, err := s.meta.Stat(logical)
- if err != nil {
- //No record: adopt the file
- sum, size, err := fileChecksum(p)
- if err != nil {
- return nil
- }
- if err := s.ensureDirs(metadata.ParentPath(logical), ""); err != nil {
- return nil
- }
- newRec := &metadata.FileRecord{
- ID: uuid.NewV4().String(),
- Path: logical,
- Size: size,
- ModTime: fi.ModTime().Unix(),
- Checksum: sum,
- Primary: vol.ID,
- }
- newRec.SetLocation(metadata.Location{VolumeID: vol.ID, NodeID: me, State: metadata.LocCommitted, Checksum: sum})
- if s.meta.Submit(metadata.KindFile, newRec) == nil {
- res.Adopted++
- }
- return nil
- }
- if rec.IsDir {
- return nil //a file where the namespace has a directory: leave it alone
- }
- loc, hasLoc := rec.Location(vol.ID)
- if hasLoc && loc.Healthy() && fi.Size() == rec.Size {
- return nil //fast path: size matches, trust it (nightly verify re-checks)
- }
- sum, size, err := fileChecksum(p)
- if err != nil {
- return nil
- }
- if sum == rec.Checksum && size == rec.Size {
- rec.SetLocation(metadata.Location{VolumeID: vol.ID, NodeID: me, State: metadata.LocCommitted, Checksum: sum})
- if rec.Primary == "" {
- rec.Primary = vol.ID
- }
- if s.meta.Submit(metadata.KindFile, rec) == nil {
- res.Attached++
- }
- return nil
- }
- //Content differs from the record
- if len(rec.HealthyLocations()) == 0 || (hasLoc && loc.NodeID == me && fi.ModTime().Unix() > rec.ModTime) {
- //No good copy anywhere (or this copy is newer): the disk wins
- rec.Size = size
- rec.Checksum = sum
- rec.ModTime = fi.ModTime().Unix()
- rec.Primary = vol.ID
- rec.Locations = nil
- rec.SetLocation(metadata.Location{VolumeID: vol.ID, NodeID: me, State: metadata.LocCommitted, Checksum: sum})
- s.meta.Submit(metadata.KindFile, rec)
- res.Attached++
- return nil
- }
- rec.SetLocation(metadata.Location{VolumeID: vol.ID, NodeID: me, State: metadata.LocStale, Checksum: sum})
- if s.meta.Submit(metadata.KindFile, rec) == nil {
- res.Stale++
- }
- return nil
- })
- if walkErr != nil {
- res.Error = walkErr.Error()
- }
- //Copies the record claims on this volume that are missing on disk
- for _, rec := range s.meta.AllFiles() {
- if rec.Removed || rec.IsDir || seen[rec.Path] {
- continue
- }
- loc, ok := rec.Location(vol.ID)
- if !ok || loc.State == metadata.LocStale || loc.State == metadata.LocWriting || loc.State == metadata.LocPending {
- continue
- }
- r := rec.Clone()
- loc.State = metadata.LocStale
- r.SetLocation(loc)
- if s.meta.Submit(metadata.KindFile, r) == nil {
- res.Stale++
- }
- }
- if res.Adopted+res.Attached+res.Stale > 0 {
- logger.PrintAndLog("Cluster", "Volume "+vol.Name+" rescanned: "+itoa(res.Adopted)+" adopted, "+itoa(res.Attached)+" attached, "+itoa(res.Stale)+" stale", nil)
- }
- return res
- }
- func itoa(v int) string {
- if v == 0 {
- return "0"
- }
- neg := v < 0
- if neg {
- v = -v
- }
- buf := [20]byte{}
- i := len(buf)
- for v > 0 {
- i--
- buf[i] = byte('0' + v%10)
- v /= 10
- }
- if neg {
- i--
- buf[i] = '-'
- }
- return string(buf[i:])
- }
|