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.

232 lines
6.7 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
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/remote_storage"
  9. "github.com/chrislusf/seaweedfs/weed/util"
  10. "github.com/golang/protobuf/jsonpb"
  11. "io"
  12. )
  13. func init() {
  14. Commands = append(Commands, &commandRemoteMount{})
  15. }
  16. type commandRemoteMount struct {
  17. }
  18. func (c *commandRemoteMount) Name() string {
  19. return "remote.mount"
  20. }
  21. func (c *commandRemoteMount) Help() string {
  22. return `mount remote storage and pull its metadata
  23. # assume a remote storage is configured to name "s3_1"
  24. remote.configure -name=s3_1 -type=s3 -access_key=xxx -secret_key=yyy
  25. # mount and pull one bucket
  26. remote.mount -dir=xxx -remote=s3_1/bucket
  27. # mount and pull one directory in the bucket
  28. remote.mount -dir=xxx -remote=s3_1/bucket/dir1
  29. `
  30. }
  31. func (c *commandRemoteMount) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  32. remoteMountCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  33. dir := remoteMountCommand.String("dir", "", "a directory in filer")
  34. nonEmpty := remoteMountCommand.Bool("nonempty", false, "allows the mounting over a non-empty directory")
  35. remote := remoteMountCommand.String("remote", "", "a directory in remote storage, ex. <storageName>/<bucket>/path/to/dir")
  36. if err = remoteMountCommand.Parse(args); err != nil {
  37. return nil
  38. }
  39. if *dir == "" {
  40. return c.listExistingRemoteStorageMounts(commandEnv, writer)
  41. }
  42. remoteStorageLocation := remote_storage.ParseLocation(*remote)
  43. // find configuration for remote storage
  44. // remotePath is /<bucket>/path/to/dir
  45. remoteConf, err := c.findRemoteStorageConfiguration(commandEnv, writer, remoteStorageLocation)
  46. if err != nil {
  47. return fmt.Errorf("find configuration for %s: %v", *remote, err)
  48. }
  49. // pull metadata from remote
  50. if err = c.pullMetadata(commandEnv, writer, *dir, *nonEmpty, remoteConf, remoteStorageLocation); err != nil {
  51. return fmt.Errorf("pull metadata: %v", err)
  52. }
  53. // store a mount configuration in filer
  54. if err = c.saveMountMapping(commandEnv, writer, *dir, remoteStorageLocation); err != nil {
  55. return fmt.Errorf("save mount mapping: %v", err)
  56. }
  57. return nil
  58. }
  59. func (c *commandRemoteMount) listExistingRemoteStorageMounts(commandEnv *CommandEnv, writer io.Writer) (err error) {
  60. // read current mapping
  61. mappings, readErr := filer.ReadMountMappings(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress)
  62. if readErr != nil {
  63. return readErr
  64. }
  65. m := jsonpb.Marshaler{
  66. EmitDefaults: false,
  67. Indent: " ",
  68. }
  69. err = m.Marshal(writer, mappings)
  70. fmt.Fprintln(writer)
  71. return
  72. }
  73. func (c *commandRemoteMount) findRemoteStorageConfiguration(commandEnv *CommandEnv, writer io.Writer, remote *filer_pb.RemoteStorageLocation) (conf *filer_pb.RemoteConf, err error) {
  74. return filer.ReadRemoteStorageConf(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress, remote.Name)
  75. }
  76. func (c *commandRemoteMount) pullMetadata(commandEnv *CommandEnv, writer io.Writer, dir string, nonEmpty bool, remoteConf *filer_pb.RemoteConf, remote *filer_pb.RemoteStorageLocation) error {
  77. // find existing directory, and ensure the directory is empty
  78. err := commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  79. parent, name := util.FullPath(dir).DirAndName()
  80. _, lookupErr := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
  81. Directory: parent,
  82. Name: name,
  83. })
  84. if lookupErr != nil {
  85. return fmt.Errorf("lookup %s: %v", dir, lookupErr)
  86. }
  87. mountToDirIsEmpty := true
  88. listErr := filer_pb.SeaweedList(client, dir, "", func(entry *filer_pb.Entry, isLast bool) error {
  89. mountToDirIsEmpty = false
  90. return nil
  91. }, "", false, 1)
  92. if listErr != nil {
  93. return fmt.Errorf("list %s: %v", dir, listErr)
  94. }
  95. if !mountToDirIsEmpty {
  96. if !nonEmpty {
  97. return fmt.Errorf("dir %s is not empty", dir)
  98. }
  99. }
  100. return nil
  101. })
  102. if err != nil {
  103. return err
  104. }
  105. // visit remote storage
  106. remoteStorage, err := remote_storage.GetRemoteStorage(remoteConf)
  107. if err != nil {
  108. return err
  109. }
  110. err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  111. ctx := context.Background()
  112. err = remoteStorage.Traverse(remote, func(remoteDir, name string, isDirectory bool, remoteEntry *filer_pb.RemoteEntry) error {
  113. localDir := dir + remoteDir
  114. println(util.NewFullPath(localDir, name))
  115. lookupResponse, lookupErr := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{
  116. Directory: localDir,
  117. Name: name,
  118. })
  119. var existingEntry *filer_pb.Entry
  120. if lookupErr != nil {
  121. if lookupErr != filer_pb.ErrNotFound {
  122. return lookupErr
  123. }
  124. } else {
  125. existingEntry = lookupResponse.Entry
  126. }
  127. if existingEntry == nil {
  128. _, createErr := client.CreateEntry(ctx, &filer_pb.CreateEntryRequest{
  129. Directory: localDir,
  130. Entry: &filer_pb.Entry{
  131. Name: name,
  132. IsDirectory: isDirectory,
  133. Attributes: &filer_pb.FuseAttributes{
  134. FileSize: uint64(remoteEntry.Size),
  135. Mtime: remoteEntry.LastModifiedAt,
  136. FileMode: uint32(0644),
  137. },
  138. RemoteEntry: remoteEntry,
  139. },
  140. })
  141. return createErr
  142. } else {
  143. if existingEntry.RemoteEntry == nil || existingEntry.RemoteEntry.ETag != remoteEntry.ETag {
  144. existingEntry.RemoteEntry = remoteEntry
  145. existingEntry.Attributes.FileSize = uint64(remoteEntry.Size)
  146. existingEntry.Attributes.Mtime = remoteEntry.LastModifiedAt
  147. _, updateErr := client.UpdateEntry(ctx, &filer_pb.UpdateEntryRequest{
  148. Directory: localDir,
  149. Entry: existingEntry,
  150. })
  151. return updateErr
  152. }
  153. }
  154. return nil
  155. })
  156. return err
  157. })
  158. if err != nil {
  159. return err
  160. }
  161. return nil
  162. }
  163. func (c *commandRemoteMount) saveMountMapping(commandEnv *CommandEnv, writer io.Writer, dir string, remoteStorageLocation *filer_pb.RemoteStorageLocation) (err error) {
  164. // read current mapping
  165. var oldContent, newContent []byte
  166. err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  167. oldContent, err = filer.ReadInsideFiler(client, filer.DirectoryEtcRemote, filer.REMOTE_STORAGE_MOUNT_FILE)
  168. return err
  169. })
  170. if err != nil {
  171. if err != filer_pb.ErrNotFound {
  172. return fmt.Errorf("read existing mapping: %v", err)
  173. }
  174. }
  175. // add new mapping
  176. newContent, err = filer.AddRemoteStorageMapping(oldContent, dir, remoteStorageLocation)
  177. if err != nil {
  178. return fmt.Errorf("add mapping %s~%s: %v", dir, remoteStorageLocation, err)
  179. }
  180. // save back
  181. err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  182. return filer.SaveInsideFiler(client, filer.DirectoryEtcRemote, filer.REMOTE_STORAGE_MOUNT_FILE, newContent)
  183. })
  184. if err != nil {
  185. return fmt.Errorf("save mapping: %v", err)
  186. }
  187. return nil
  188. }