github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/fs/deleted.go (about) 1 // Package fs provides mountpath and FQN abstractions and methods to resolve/map stored content 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package fs 6 7 import ( 8 "errors" 9 "fmt" 10 "os" 11 "path/filepath" 12 "strconv" 13 "syscall" 14 "time" 15 16 "github.com/NVIDIA/aistore/cmn/cos" 17 "github.com/NVIDIA/aistore/cmn/debug" 18 "github.com/NVIDIA/aistore/cmn/fname" 19 "github.com/NVIDIA/aistore/cmn/mono" 20 "github.com/NVIDIA/aistore/cmn/nlog" 21 ) 22 23 // TODO: undelete (feature) 24 25 const ( 26 deletedRoot = ".$deleted" 27 desleep = 256 * time.Millisecond 28 deretries = 3 29 ) 30 31 func (mi *Mountpath) DeletedRoot() string { 32 return filepath.Join(mi.Path, deletedRoot) 33 } 34 35 func (mi *Mountpath) TempDir(dir string) string { 36 return filepath.Join(mi.Path, deletedRoot, dir) 37 } 38 39 func (mi *Mountpath) RemoveDeleted(who string) (rerr error) { 40 delroot := mi.DeletedRoot() 41 dentries, err := os.ReadDir(delroot) 42 if err != nil { 43 if os.IsNotExist(err) { 44 cos.CreateDir(delroot) 45 err = nil 46 } 47 return err 48 } 49 for _, dent := range dentries { 50 fqn := filepath.Join(delroot, dent.Name()) 51 if !dent.IsDir() { 52 err := fmt.Errorf("%s: unexpected non-directory item %q in 'deleted'", who, fqn) 53 debug.AssertNoErr(err) 54 nlog.Errorln(err) 55 continue 56 } 57 if err = os.RemoveAll(fqn); err == nil { 58 continue 59 } 60 if !os.IsNotExist(err) { 61 nlog.Errorf("%s: failed to remove %q from 'deleted', err %v", who, fqn, err) 62 if rerr == nil { 63 rerr = err 64 } 65 } 66 } 67 return 68 } 69 70 // MoveToDeleted removes directory in steps: 71 // 1. Synchronously gets temporary directory name 72 // 2. Synchronously renames old folder to temporary directory 73 func (mi *Mountpath) MoveToDeleted(dir string) (err error) { 74 var base, tmpBase, tmpDst string 75 err = cos.Stat(dir) 76 if err != nil { 77 if os.IsNotExist(err) { 78 err = nil 79 } 80 return 81 } 82 83 var ( 84 cs = Cap() 85 errCap, oos = cs.Err(), cs.IsOOS() 86 ) 87 if errCap != nil { 88 goto rm // not moving - removing 89 } 90 base = filepath.Base(dir) 91 tmpBase = mi.TempDir(base) 92 err = cos.CreateDir(tmpBase) 93 if err != nil { 94 if cos.IsErrOOS(err) { 95 oos = true 96 } 97 goto rm 98 } 99 100 tmpDst = filepath.Join(tmpBase, strconv.FormatInt(mono.NanoTime(), 10)) 101 if err = os.Rename(dir, tmpDst); err == nil { 102 return // ok 103 } 104 105 if cos.IsErrOOS(err) { 106 oos = true 107 } 108 rm: 109 // not placing in 'deleted' - removing right away 110 errRm := RemoveAll(dir) 111 if err == nil { 112 err = errRm 113 } 114 if oos { 115 nlog.Errorf("%s %s: OOS (%v)", mi, cs.String(), err) 116 } 117 return err 118 } 119 120 func (mi *Mountpath) ClearMDs(inclBMD bool) (rerr error) { 121 for _, mdfd := range mdFilesDirs { 122 if !inclBMD && mdfd == fname.Bmd { 123 continue 124 } 125 fpath := filepath.Join(mi.Path, mdfd) 126 if err := RemoveAll(fpath); err != nil { 127 nlog.Errorln(err) 128 rerr = err 129 } 130 } 131 return 132 } 133 134 // 135 // decommission 136 // 137 138 func Decommission(mdOnly bool) { 139 var ( 140 avail, disabled = Get() 141 allmpi = []MPI{avail, disabled} 142 ) 143 for i := range deretries { // retry 144 if mdOnly { 145 if err := demd(allmpi); err == nil { 146 return 147 } 148 } else { 149 if err := deworld(allmpi); err == nil { 150 return 151 } 152 } 153 if i < deretries-1 { 154 nlog.Errorln("decommission: retrying cleanup...") 155 time.Sleep(desleep) 156 } 157 } 158 } 159 160 func demd(allmpi []MPI) (rerr error) { 161 for _, mpi := range allmpi { 162 for _, mi := range mpi { 163 // NOTE: BMD goes with data (ie., no data - no BMD) 164 if err := mi.ClearMDs(false /*include BMD*/); err != nil { 165 rerr = err 166 } 167 // node ID (SID) 168 if err := removeXattr(mi.Path, nodeXattrID); err != nil { 169 debug.AssertNoErr(err) 170 rerr = err 171 } 172 } 173 } 174 return 175 } 176 177 // the entire content including user data, MDs, and daemon ID 178 func deworld(allmpi []MPI) (rerr error) { 179 for _, mpi := range allmpi { 180 for _, mi := range mpi { 181 if err := os.RemoveAll(mi.Path); err != nil { 182 debug.Assert(!os.IsNotExist(err)) 183 // retry ENOTEMPTY in place 184 if errors.Is(err, syscall.ENOTEMPTY) { 185 time.Sleep(desleep) 186 err = os.RemoveAll(mi.Path) 187 } 188 if err != nil { 189 nlog.Errorln(err) 190 rerr = err 191 } 192 } 193 } 194 } 195 return 196 } 197 198 // retrying ENOTEMPTY - "directory not empty" race vs. new writes 199 func RemoveAll(dir string) (err error) { 200 for i := range deretries { 201 err = os.RemoveAll(dir) 202 if err == nil { 203 break 204 } 205 debug.Assert(!os.IsNotExist(err), err) 206 nlog.ErrorDepth(1, err) 207 if !errors.Is(err, syscall.ENOTEMPTY) { 208 break 209 } 210 if i < deretries-1 { 211 time.Sleep(desleep) 212 } 213 } 214 return 215 }