memorystorage.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package storage
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "sync"
  7. "github.com/opencost/opencost/core/pkg/log"
  8. "github.com/opencost/opencost/core/pkg/storage/memfile"
  9. )
  10. // MemoryStorage is a thread-safe in-memory file system storage implementation. It can be used for testing storage.Storage dependents
  11. // or to serve as a lightweight storage implementation within a production system.
  12. type MemoryStorage struct {
  13. lock sync.Mutex
  14. directPaths map[string]*memfile.MemoryFile
  15. fileTree *memfile.MemoryDirectory
  16. }
  17. // NewMemoryStorage creates a new in-memory file system storage implementation.
  18. func NewMemoryStorage() *MemoryStorage {
  19. return &MemoryStorage{
  20. directPaths: make(map[string]*memfile.MemoryFile),
  21. fileTree: memfile.NewMemoryDirectory(""),
  22. }
  23. }
  24. // String returns the storage type as a string for logging purposes.
  25. func (ms *MemoryStorage) String() string {
  26. return string(ms.StorageType())
  27. }
  28. // StorageType returns a string identifier for the type of storage used by the implementation.
  29. func (ms *MemoryStorage) StorageType() StorageType {
  30. return StorageTypeMemory
  31. }
  32. // FullPath returns the storage working path combined with the path provided
  33. func (ms *MemoryStorage) FullPath(path string) string {
  34. return path
  35. }
  36. // Stat returns the StorageStats for the specific path.
  37. func (ms *MemoryStorage) Stat(path string) (*StorageInfo, error) {
  38. ms.lock.Lock()
  39. defer ms.lock.Unlock()
  40. path = filepath.Clean(path)
  41. if file, ok := ms.directPaths[path]; ok {
  42. return &StorageInfo{
  43. Name: file.Name,
  44. Size: file.Size(),
  45. ModTime: file.ModTime,
  46. }, nil
  47. }
  48. return nil, DoesNotExistError
  49. }
  50. // Read uses the relative path of the storage combined with the provided path to
  51. // read the contents.
  52. func (ms *MemoryStorage) Read(path string) ([]byte, error) {
  53. ms.lock.Lock()
  54. defer ms.lock.Unlock()
  55. path = filepath.Clean(path)
  56. if file, ok := ms.directPaths[path]; ok {
  57. return file.Contents, nil
  58. }
  59. return nil, DoesNotExistError
  60. }
  61. // ReadToLocalFile writes the specified object at path to destPath on the local file system.
  62. func (ms *MemoryStorage) ReadToLocalFile(path, destPath string) error {
  63. ms.lock.Lock()
  64. path = filepath.Clean(path)
  65. file, ok := ms.directPaths[path]
  66. if !ok {
  67. ms.lock.Unlock()
  68. return DoesNotExistError
  69. }
  70. // Copy the contents so we can release the lock before doing potentially slow disk IO.
  71. data := append([]byte(nil), file.Contents...)
  72. ms.lock.Unlock()
  73. dir := filepath.Dir(destPath)
  74. if err := os.MkdirAll(dir, os.ModePerm); err != nil {
  75. return fmt.Errorf("MemoryStorage: ReadToLocalFile: creating destination directory: %w", err)
  76. }
  77. if err := os.WriteFile(destPath, data, 0600); err != nil {
  78. return fmt.Errorf("MemoryStorage: ReadToLocalFile: writing destination file: %w", err)
  79. }
  80. return nil
  81. }
  82. // Write uses the relative path of the storage combined with the provided path
  83. // to write a new file or overwrite an existing file.
  84. func (ms *MemoryStorage) Write(path string, data []byte) error {
  85. ms.lock.Lock()
  86. defer ms.lock.Unlock()
  87. paths, pFile := memfile.Split(path)
  88. f := memfile.NewMemoryFile(pFile, data)
  89. currentDir := memfile.CreateSubdirectory(ms.fileTree, paths)
  90. currentDir.AddFile(f)
  91. ms.directPaths[path] = f
  92. return nil
  93. }
  94. // Remove uses the relative path of the storage combined with the provided path to
  95. // remove a file from storage permanently.
  96. func (ms *MemoryStorage) Remove(path string) error {
  97. ms.lock.Lock()
  98. defer ms.lock.Unlock()
  99. path = filepath.Clean(path)
  100. paths, pFile := memfile.Split(path)
  101. currentDir, err := memfile.FindSubdirectory(ms.fileTree, paths)
  102. if err != nil {
  103. return fmt.Errorf("file not found: %s - %w", path, DoesNotExistError)
  104. }
  105. currentDir.RemoveFile(pFile)
  106. delete(ms.directPaths, path)
  107. return nil
  108. }
  109. // Exists uses the relative path of the storage combined with the provided path to
  110. // determine if the file exists.
  111. func (ms *MemoryStorage) Exists(path string) (bool, error) {
  112. ms.lock.Lock()
  113. defer ms.lock.Unlock()
  114. path = filepath.Clean(path)
  115. _, ok := ms.directPaths[path]
  116. return ok, nil
  117. }
  118. // List uses the relative path of the storage combined with the provided path to return
  119. // storage information for the files.
  120. func (ms *MemoryStorage) List(path string) ([]*StorageInfo, error) {
  121. ms.lock.Lock()
  122. defer ms.lock.Unlock()
  123. paths := memfile.SplitPaths(path)
  124. currentDir, err := memfile.FindSubdirectory(ms.fileTree, paths)
  125. if err != nil {
  126. // contract for bucket storages returns an empty list in this case
  127. // so just log a warning, and return an empty list
  128. log.Warnf("failed to resolve path: %s - %s", path, err)
  129. return []*StorageInfo{}, nil
  130. }
  131. storageInfos := make([]*StorageInfo, 0, currentDir.FileCount())
  132. for f := range currentDir.Files() {
  133. storageInfos = append(storageInfos, &StorageInfo{
  134. Name: f.Name,
  135. Size: f.Size(),
  136. ModTime: f.ModTime,
  137. })
  138. }
  139. return storageInfos, nil
  140. }
  141. // ListDirectories uses the relative path of the storage combined with the provided path
  142. // to return storage information for only directories contained along the path. This
  143. // functions as List, but returns storage information for only directories.
  144. func (ms *MemoryStorage) ListDirectories(path string) ([]*StorageInfo, error) {
  145. ms.lock.Lock()
  146. defer ms.lock.Unlock()
  147. paths := memfile.SplitPaths(path)
  148. currentDir, err := memfile.FindSubdirectory(ms.fileTree, paths)
  149. if err != nil {
  150. // contract for bucket storages returns an empty list in this case
  151. // so just log a warning, and return an empty list
  152. log.Warnf("failed to resolve path: %s - %s", path, err)
  153. return []*StorageInfo{}, nil
  154. }
  155. storageInfos := make([]*StorageInfo, 0, currentDir.DirCount())
  156. for d := range currentDir.Directories() {
  157. storageInfos = append(storageInfos, &StorageInfo{
  158. Name: filepath.Join(append(paths, d.Name)...) + "/",
  159. Size: d.Size(),
  160. ModTime: d.ModTime,
  161. })
  162. }
  163. return storageInfos, nil
  164. }