github.com/uber/kraken@v0.1.4/lib/store/base/file_op.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package base
    15  
    16  import (
    17  	"fmt"
    18  	"os"
    19  	"strings"
    20  
    21  	"github.com/uber/kraken/lib/store/metadata"
    22  )
    23  
    24  type lockLevel int
    25  
    26  const (
    27  	// lockLevelPeek indicates lock for peek.
    28  	_lockLevelPeek lockLevel = iota
    29  	// lockLevelRead indicates lock for read.
    30  	_lockLevelRead
    31  	// lockLevelWrite indicates lock for read.
    32  	_lockLevelWrite
    33  )
    34  
    35  // FileOp performs one file or metadata operation on FileStore, given a list of
    36  // acceptable states.
    37  type FileOp interface {
    38  	AcceptState(state FileState) FileOp
    39  	GetAcceptableStates() map[FileState]interface{}
    40  
    41  	CreateFile(name string, createState FileState, len int64) error
    42  	MoveFileFrom(name string, createState FileState, sourcePath string) error
    43  	MoveFile(name string, goalState FileState) error
    44  	LinkFileTo(name string, targetPath string) error
    45  	DeleteFile(name string) error
    46  
    47  	GetFilePath(name string) (string, error)
    48  	GetFileStat(name string) (os.FileInfo, error)
    49  
    50  	GetFileReader(name string) (FileReader, error)
    51  	GetFileReadWriter(name string) (FileReadWriter, error)
    52  
    53  	GetFileMetadata(name string, md metadata.Metadata) error
    54  	SetFileMetadata(name string, md metadata.Metadata) (bool, error)
    55  	SetFileMetadataAt(name string, md metadata.Metadata, b []byte, offset int64) (bool, error)
    56  	GetOrSetFileMetadata(name string, md metadata.Metadata) error
    57  	DeleteFileMetadata(name string, md metadata.Metadata) error
    58  
    59  	RangeFileMetadata(name string, f func(metadata.Metadata) error) error
    60  
    61  	ListNames() ([]string, error)
    62  
    63  	String() string
    64  }
    65  
    66  var _ FileOp = (*localFileOp)(nil)
    67  
    68  // localFileOp is a short-lived obj that performs one file or metadata operation
    69  // on local disk, given a list of acceptable states.
    70  type localFileOp struct {
    71  	s      *localFileStore
    72  	states map[FileState]interface{} // Set of states that's acceptable.
    73  }
    74  
    75  // NewLocalFileOp inits a new FileOp obj.
    76  func NewLocalFileOp(s *localFileStore) FileOp {
    77  	return &localFileOp{
    78  		s:      s,
    79  		states: make(map[FileState]interface{}),
    80  	}
    81  }
    82  
    83  // AcceptState adds a new state to the acceptable states list.
    84  func (op *localFileOp) AcceptState(state FileState) FileOp {
    85  	op.states[state] = struct{}{}
    86  	return op
    87  }
    88  
    89  // GetAcceptableStates returns a set of acceptable states.
    90  func (op *localFileOp) GetAcceptableStates() map[FileState]interface{} {
    91  	return op.states
    92  }
    93  
    94  // verifyStateHelper verifies file is in one of the acceptable states.
    95  func (op *localFileOp) verifyStateHelper(name string, entry FileEntry) error {
    96  	currState := entry.GetState()
    97  	for state := range op.states {
    98  		if currState == state {
    99  			// File is in one of the acceptable states.
   100  			return nil
   101  		}
   102  	}
   103  	return &FileStateError{
   104  		Op:    "verifyStateHelper",
   105  		Name:  name,
   106  		State: currState,
   107  		Msg:   fmt.Sprintf("desired states: %v", op.states),
   108  	}
   109  }
   110  
   111  // reloadFileEntryHelper tries to reload file from disk into memory.
   112  // Note it doesn't try to verify states or reload file from all possible states.
   113  // If reload succeeded, return true;
   114  // If file already exists in memory, return false;
   115  // If file is neither in memory or on disk, return false with os.ErrNotExist.
   116  func (op *localFileOp) reloadFileEntryHelper(name string) (reloaded bool, err error) {
   117  	if op.s.fileMap.Contains(name) {
   118  		return false, nil
   119  	}
   120  
   121  	// Check if file exists on disk.
   122  	// TODO: The states need to be guaranteed to be topologically sorted.
   123  	for state := range op.states {
   124  		fileEntry, err := op.s.fileEntryFactory.Create(name, state)
   125  		if err != nil {
   126  			return false, fmt.Errorf("create: %s", err)
   127  		}
   128  
   129  		// Try load before acquiring lock first.
   130  		if err = fileEntry.Reload(); err != nil {
   131  			continue
   132  		}
   133  		// Try to store file entry into memory.
   134  		if stored := op.s.fileMap.TryStore(name, fileEntry, func(name string, entry FileEntry) bool {
   135  			// Verify the file is still on disk.
   136  			err = entry.Reload()
   137  			return err == nil
   138  		}); err != nil {
   139  			if os.IsNotExist(err) {
   140  				continue
   141  			}
   142  			return false, err
   143  		} else if !stored {
   144  			// The entry was just reloaded by another goroutine, return true.
   145  			// Since TryStore() updates LAT of existing entry, it's unlikely
   146  			// that the entry would be deleted before this function returns.
   147  			return true, nil
   148  		}
   149  		return true, nil
   150  	}
   151  	return false, os.ErrNotExist
   152  }
   153  
   154  // lockHelper runs f under protection of entry level RWMutex.
   155  func (op *localFileOp) lockHelper(
   156  	name string, l lockLevel, f func(name string, entry FileEntry)) (err error) {
   157  	if _, err = op.reloadFileEntryHelper(name); err != nil {
   158  		return err
   159  	}
   160  	var loaded bool
   161  	if l == _lockLevelPeek {
   162  		loaded = op.s.fileMap.LoadForPeek(name, func(name string, entry FileEntry) {
   163  			if err = op.verifyStateHelper(name, entry); err != nil {
   164  				return
   165  			}
   166  			f(name, entry)
   167  		})
   168  	} else if l == _lockLevelRead {
   169  		loaded = op.s.fileMap.LoadForRead(name, func(name string, entry FileEntry) {
   170  			if err = op.verifyStateHelper(name, entry); err != nil {
   171  				return
   172  			}
   173  			f(name, entry)
   174  		})
   175  	} else if l == _lockLevelWrite {
   176  		loaded = op.s.fileMap.LoadForWrite(name, func(name string, entry FileEntry) {
   177  			if err = op.verifyStateHelper(name, entry); err != nil {
   178  				return
   179  			}
   180  			f(name, entry)
   181  		})
   182  	}
   183  	if !loaded {
   184  		return os.ErrNotExist
   185  	}
   186  	return err
   187  }
   188  
   189  func (op *localFileOp) deleteHelper(
   190  	name string, f func(name string, entry FileEntry) bool) (err error) {
   191  	if _, err = op.reloadFileEntryHelper(name); err != nil {
   192  		return err
   193  	}
   194  	op.s.fileMap.Delete(name, func(name string, entry FileEntry) bool {
   195  		err = op.verifyStateHelper(name, entry)
   196  		if err != nil {
   197  			return false
   198  		}
   199  
   200  		return f(name, entry)
   201  	})
   202  	return err
   203  }
   204  
   205  // createFileHelper is a helper function that adds a new file to store.
   206  // it either moves the new file from a unmanaged location, or creates an empty
   207  // file with specified size.
   208  // If file exists and is in an acceptable state, returns os.ErrExist.
   209  // If file exists but not in an acceptable state, returns FileStateError.
   210  func (op *localFileOp) createFileHelper(
   211  	name string, targetState FileState, sourcePath string, len int64) (err error) {
   212  	// Check if file exists in in-memory map and is in an acceptable state.
   213  	loaded := op.s.fileMap.LoadForRead(name, func(name string, entry FileEntry) {
   214  		err = op.verifyStateHelper(name, entry)
   215  	})
   216  	if err != nil && !os.IsNotExist(err) {
   217  		// Includes FileStateError.
   218  		return err
   219  	} else if loaded {
   220  		return os.ErrExist
   221  	}
   222  
   223  	// Check if file is on disk.
   224  	loaded, err = op.reloadFileEntryHelper(name)
   225  	if err != nil && !os.IsNotExist(err) {
   226  		// Includes FileStateError.
   227  		return err
   228  	} else if loaded {
   229  		return os.ErrExist
   230  	}
   231  
   232  	// Create new file entry.
   233  	err = nil
   234  	newEntry, err := op.s.fileEntryFactory.Create(name, targetState)
   235  	if err != nil {
   236  		return fmt.Errorf("create: %s", err)
   237  	}
   238  	if stored := op.s.fileMap.TryStore(name, newEntry, func(name string, entry FileEntry) bool {
   239  		if sourcePath != "" {
   240  			err = newEntry.MoveFrom(targetState, sourcePath)
   241  			if err != nil {
   242  				return false
   243  			}
   244  		} else {
   245  			err = newEntry.Create(targetState, len)
   246  			if err != nil {
   247  				return false
   248  			}
   249  		}
   250  		return true
   251  	}); err != nil {
   252  		return err
   253  	} else if !stored {
   254  		// Another goroutine created the entry before this one, verify again for
   255  		// correct error message.
   256  		// Since TryStore() updates LAT of existing entry, it's unlikely that
   257  		// the entry would be deleted before this function returns.
   258  		if loadErr := op.lockHelper(name, _lockLevelRead, func(name string, entry FileEntry) {
   259  			return
   260  		}); loadErr != nil {
   261  			return loadErr
   262  		}
   263  		return os.ErrExist
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  // CreateFile creates an empty file with specified size.
   270  // If file exists and is in an acceptable state, returns os.ErrExist.
   271  // If file exists but not in an acceptable state, returns FileStateError.
   272  func (op *localFileOp) CreateFile(name string, targetState FileState, len int64) (err error) {
   273  	return op.createFileHelper(name, targetState, "", len)
   274  }
   275  
   276  // MoveFileFrom moves an unmanaged file into file store.
   277  // If file exists and is in an acceptable state, returns os.ErrExist.
   278  // If file exists but not in an acceptable state, returns FileStateError.
   279  func (op *localFileOp) MoveFileFrom(name string, targetState FileState, sourcePath string) (err error) {
   280  	return op.createFileHelper(name, targetState, sourcePath, -1)
   281  }
   282  
   283  // MoveFile moves a file to a different directory and updates its state
   284  // accordingly, and moves all metadata that's `movable`.
   285  func (op *localFileOp) MoveFile(name string, targetState FileState) (err error) {
   286  	if _, err = op.reloadFileEntryHelper(name); err != nil {
   287  		return err
   288  	}
   289  
   290  	// Verify that the file is not in target state, and is currently in one of
   291  	// the acceptable states.
   292  	loaded := op.s.fileMap.LoadForWrite(name, func(name string, entry FileEntry) {
   293  		currState := entry.GetState()
   294  		if currState == targetState {
   295  			err = os.ErrExist
   296  			return
   297  		}
   298  		for state := range op.states {
   299  			if currState == state {
   300  				// File is in one of the acceptable states. Perform move.
   301  				err = entry.Move(targetState)
   302  				return
   303  			}
   304  		}
   305  		err = &FileStateError{
   306  			Op:    "MoveFile",
   307  			State: currState,
   308  			Name:  name,
   309  			Msg:   fmt.Sprintf("desired states: %v", op.states),
   310  		}
   311  	})
   312  	if !loaded {
   313  		return os.ErrNotExist
   314  	}
   315  	return err
   316  }
   317  
   318  // LinkFileTo create a hardlink to an unmanaged path.
   319  func (op *localFileOp) LinkFileTo(name string, targetPath string) (err error) {
   320  	if loadErr := op.lockHelper(name, _lockLevelRead, func(name string, entry FileEntry) {
   321  		err = entry.LinkTo(targetPath)
   322  	}); loadErr != nil {
   323  		return loadErr
   324  	}
   325  	return err
   326  }
   327  
   328  // DeleteFile removes a file from disk and file map.
   329  func (op *localFileOp) DeleteFile(name string) (err error) {
   330  	if loadErr := op.deleteHelper(name, func(name string, entry FileEntry) bool {
   331  		err = entry.Delete()
   332  		// Return true so the entry would be removed from map regardless.
   333  		return true
   334  	}); loadErr != nil {
   335  		return loadErr
   336  	}
   337  	return err
   338  }
   339  
   340  // GetFilePath returns full path for a file.
   341  func (op *localFileOp) GetFilePath(name string) (path string, err error) {
   342  	if loadErr := op.lockHelper(name, _lockLevelPeek, func(name string, entry FileEntry) {
   343  		path = entry.GetPath()
   344  	}); loadErr != nil {
   345  		return "", loadErr
   346  	}
   347  	return path, nil
   348  }
   349  
   350  // GetFileStat returns FileInfo for a file.
   351  func (op *localFileOp) GetFileStat(name string) (info os.FileInfo, err error) {
   352  	if loadErr := op.lockHelper(name, _lockLevelPeek, func(name string, entry FileEntry) {
   353  		info, err = entry.GetStat()
   354  	}); loadErr != nil {
   355  		return nil, loadErr
   356  	}
   357  	return info, err
   358  }
   359  
   360  // GetFileReader returns a FileReader object for read operations.
   361  func (op *localFileOp) GetFileReader(name string) (r FileReader, err error) {
   362  	if loadErr := op.lockHelper(name, _lockLevelRead, func(name string, entry FileEntry) {
   363  		r, err = entry.GetReader()
   364  	}); loadErr != nil {
   365  		return nil, loadErr
   366  	}
   367  	return r, err
   368  }
   369  
   370  // GetFileReadWriter returns a FileReadWriter object for read/write operations.
   371  func (op *localFileOp) GetFileReadWriter(name string) (w FileReadWriter, err error) {
   372  	if loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) {
   373  		w, err = entry.GetReadWriter()
   374  	}); loadErr != nil {
   375  		return nil, loadErr
   376  	}
   377  	return w, err
   378  }
   379  
   380  // GetFileMetadata loads metadata assocciated with the file.
   381  func (op *localFileOp) GetFileMetadata(name string, md metadata.Metadata) (err error) {
   382  	if loadErr := op.lockHelper(name, _lockLevelPeek, func(name string, entry FileEntry) {
   383  		err = entry.GetMetadata(md)
   384  	}); loadErr != nil {
   385  		return loadErr
   386  	}
   387  	return err
   388  }
   389  
   390  // SetFileMetadata creates or overwrites metadata assocciate with the file.
   391  func (op *localFileOp) SetFileMetadata(name string, md metadata.Metadata) (updated bool, err error) {
   392  	if loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) {
   393  		updated, err = entry.SetMetadata(md)
   394  	}); loadErr != nil {
   395  		return false, loadErr
   396  	}
   397  	return updated, err
   398  }
   399  
   400  // SetFileMetadataAt overwrites metadata assocciate with the file with content.
   401  func (op *localFileOp) SetFileMetadataAt(
   402  	name string, md metadata.Metadata, b []byte, offset int64) (updated bool, err error) {
   403  
   404  	if loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) {
   405  		updated, err = entry.SetMetadataAt(md, b, offset)
   406  	}); loadErr != nil {
   407  		return false, loadErr
   408  	}
   409  	return updated, err
   410  }
   411  
   412  // GetOrSetFileMetadata see localFileEntryInternal.
   413  func (op *localFileOp) GetOrSetFileMetadata(name string, md metadata.Metadata) (err error) {
   414  	if loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) {
   415  		err = entry.GetOrSetMetadata(md)
   416  	}); loadErr != nil {
   417  		return loadErr
   418  	}
   419  	return err
   420  }
   421  
   422  // DeleteFileMetadata deletes metadata of the specified type for a file.
   423  func (op *localFileOp) DeleteFileMetadata(name string, md metadata.Metadata) (err error) {
   424  	loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) {
   425  		err = entry.DeleteMetadata(md)
   426  	})
   427  	if loadErr != nil {
   428  		return loadErr
   429  	}
   430  	return err
   431  }
   432  
   433  // RangeFileMetadata loops through all metadata of one file and applies function f, until an error happens.
   434  func (op *localFileOp) RangeFileMetadata(name string, f func(md metadata.Metadata) error) (err error) {
   435  	loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) {
   436  		err = entry.RangeMetadata(f)
   437  	})
   438  	if loadErr != nil {
   439  		return loadErr
   440  	}
   441  	return err
   442  }
   443  
   444  func (op *localFileOp) ListNames() ([]string, error) {
   445  	var names []string
   446  	for state := range op.states {
   447  		stateNames, err := op.s.fileEntryFactory.ListNames(state)
   448  		if err != nil {
   449  			return nil, err
   450  		}
   451  		names = append(names, stateNames...)
   452  	}
   453  	return names, nil
   454  }
   455  
   456  func (op *localFileOp) String() string {
   457  	var dirs []string
   458  	for state := range op.states {
   459  		dirs = append(dirs, state.GetDirectory())
   460  	}
   461  	return fmt.Sprintf("{%s}", strings.Join(dirs, ", "))
   462  }