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  }