You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

173 lines
4.6 KiB

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