command_remote_uncache.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package shell
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "path/filepath"
  8. "strings"
  9. "time"
  10. "github.com/seaweedfs/seaweedfs/weed/filer"
  11. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  12. "github.com/seaweedfs/seaweedfs/weed/util"
  13. )
  14. func init() {
  15. Commands = append(Commands, &commandRemoteUncache{})
  16. }
  17. type commandRemoteUncache struct {
  18. }
  19. func (c *commandRemoteUncache) Name() string {
  20. return "remote.uncache"
  21. }
  22. func (c *commandRemoteUncache) Help() string {
  23. return `keep the metadata but remote cache the file content for mounted directories or files
  24. This is designed to run regularly. So you can add it to some cronjob.
  25. If a file is not synchronized with the remote copy, the file will be skipped to avoid loss of data.
  26. remote.uncache -dir=/xxx
  27. remote.uncache -dir=/xxx/some/sub/dir
  28. remote.uncache -dir=/xxx/some/sub/dir -include=*.pdf
  29. remote.uncache -dir=/xxx/some/sub/dir -exclude=*.txt
  30. remote.uncache -minSize=1024000 # uncache files larger than 100K
  31. remote.uncache -minAge=3600 # uncache files older than 1 hour
  32. `
  33. }
  34. func (c *commandRemoteUncache) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  35. remoteUncacheCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  36. dir := remoteUncacheCommand.String("dir", "", "a directory in filer")
  37. fileFiler := newFileFilter(remoteUncacheCommand)
  38. if err = remoteUncacheCommand.Parse(args); err != nil {
  39. return nil
  40. }
  41. mappings, listErr := filer.ReadMountMappings(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress)
  42. if listErr != nil {
  43. return listErr
  44. }
  45. if *dir != "" {
  46. var localMountedDir string
  47. for k := range mappings.Mappings {
  48. if strings.HasPrefix(*dir, k) {
  49. localMountedDir = k
  50. }
  51. }
  52. if localMountedDir == "" {
  53. jsonPrintln(writer, mappings)
  54. fmt.Fprintf(writer, "%s is not mounted\n", *dir)
  55. return nil
  56. }
  57. // pull content from remote
  58. if err = c.uncacheContentData(commandEnv, writer, util.FullPath(*dir), fileFiler); err != nil {
  59. return fmt.Errorf("uncache content data: %v", err)
  60. }
  61. return nil
  62. }
  63. for key, _ := range mappings.Mappings {
  64. if err := c.uncacheContentData(commandEnv, writer, util.FullPath(key), fileFiler); err != nil {
  65. return err
  66. }
  67. }
  68. return nil
  69. }
  70. func (c *commandRemoteUncache) uncacheContentData(commandEnv *CommandEnv, writer io.Writer, dirToCache util.FullPath, fileFilter *FileFilter) error {
  71. return recursivelyTraverseDirectory(commandEnv, dirToCache, func(dir util.FullPath, entry *filer_pb.Entry) bool {
  72. if !mayHaveCachedToLocal(entry) {
  73. return true // true means recursive traversal should continue
  74. }
  75. if !fileFilter.matches(entry) {
  76. return true
  77. }
  78. if entry.RemoteEntry.LastLocalSyncTsNs/1e9 < entry.Attributes.Mtime {
  79. return true // should not uncache an entry that is not synchronized with remote
  80. }
  81. entry.RemoteEntry.LastLocalSyncTsNs = 0
  82. entry.Chunks = nil
  83. fmt.Fprintf(writer, "Uncache %+v ... ", dir.Child(entry.Name))
  84. err := commandEnv.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  85. _, updateErr := client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
  86. Directory: string(dir),
  87. Entry: entry,
  88. })
  89. return updateErr
  90. })
  91. if err != nil {
  92. fmt.Fprintf(writer, "uncache %+v: %v\n", dir.Child(entry.Name), err)
  93. return false
  94. }
  95. fmt.Fprintf(writer, "Done\n")
  96. return true
  97. })
  98. }
  99. type FileFilter struct {
  100. include *string
  101. exclude *string
  102. minSize *int64
  103. maxSize *int64
  104. minAge *int64
  105. maxAge *int64
  106. }
  107. func newFileFilter(remoteMountCommand *flag.FlagSet) (ff *FileFilter) {
  108. ff = &FileFilter{}
  109. ff.include = remoteMountCommand.String("include", "", "pattens of file names, e.g., *.pdf, *.html, ab?d.txt")
  110. ff.exclude = remoteMountCommand.String("exclude", "", "pattens of file names, e.g., *.pdf, *.html, ab?d.txt")
  111. ff.minSize = remoteMountCommand.Int64("minSize", -1, "minimum file size in bytes")
  112. ff.maxSize = remoteMountCommand.Int64("maxSize", -1, "maximum file size in bytes")
  113. ff.minAge = remoteMountCommand.Int64("minAge", -1, "minimum file age in seconds")
  114. ff.maxAge = remoteMountCommand.Int64("maxAge", -1, "maximum file age in seconds")
  115. return
  116. }
  117. func (ff *FileFilter) matches(entry *filer_pb.Entry) bool {
  118. if *ff.include != "" {
  119. if ok, _ := filepath.Match(*ff.include, entry.Name); !ok {
  120. return false
  121. }
  122. }
  123. if *ff.exclude != "" {
  124. if ok, _ := filepath.Match(*ff.exclude, entry.Name); ok {
  125. return false
  126. }
  127. }
  128. if *ff.minSize != -1 {
  129. if int64(entry.Attributes.FileSize) < *ff.minSize {
  130. return false
  131. }
  132. }
  133. if *ff.maxSize != -1 {
  134. if int64(entry.Attributes.FileSize) > *ff.maxSize {
  135. return false
  136. }
  137. }
  138. if *ff.minAge != -1 {
  139. if entry.Attributes.Crtime + *ff.minAge > time.Now().Unix() {
  140. return false
  141. }
  142. }
  143. if *ff.maxAge != -1 {
  144. if entry.Attributes.Crtime + *ff.maxAge < time.Now().Unix() {
  145. return false
  146. }
  147. }
  148. return true
  149. }