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.

229 lines
6.7 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 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. return m.Marshal(writer, mappings)
  70. }
  71. func (c *commandRemoteMount) findRemoteStorageConfiguration(commandEnv *CommandEnv, writer io.Writer, remote *filer_pb.RemoteStorageLocation) (conf *filer_pb.RemoteConf, err error) {
  72. return filer.ReadRemoteStorageConf(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress, remote.Name)
  73. }
  74. func (c *commandRemoteMount) pullMetadata(commandEnv *CommandEnv, writer io.Writer, dir string, nonEmpty bool, remoteConf *filer_pb.RemoteConf, remote *filer_pb.RemoteStorageLocation) error {
  75. // find existing directory, and ensure the directory is empty
  76. err := commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  77. parent, name := util.FullPath(dir).DirAndName()
  78. _, lookupErr := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
  79. Directory: parent,
  80. Name: name,
  81. })
  82. if lookupErr != nil {
  83. return fmt.Errorf("lookup %s: %v", dir, lookupErr)
  84. }
  85. mountToDirIsEmpty := true
  86. listErr := filer_pb.SeaweedList(client, dir, "", func(entry *filer_pb.Entry, isLast bool) error {
  87. mountToDirIsEmpty = false
  88. return nil
  89. }, "", false, 1)
  90. if listErr != nil {
  91. return fmt.Errorf("list %s: %v", dir, listErr)
  92. }
  93. if !mountToDirIsEmpty {
  94. if !nonEmpty {
  95. return fmt.Errorf("dir %s is not empty", dir)
  96. }
  97. }
  98. return nil
  99. })
  100. if err != nil {
  101. return err
  102. }
  103. // visit remote storage
  104. remoteStorage, err := remote_storage.GetRemoteStorage(remoteConf)
  105. if err != nil {
  106. return err
  107. }
  108. err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  109. ctx := context.Background()
  110. err = remoteStorage.Traverse(remote, func(remoteDir, name string, isDirectory bool, remoteEntry *filer_pb.RemoteEntry) error {
  111. localDir := dir + remoteDir
  112. println(util.NewFullPath(localDir, name))
  113. lookupResponse, lookupErr := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{
  114. Directory: localDir,
  115. Name: name,
  116. })
  117. var existingEntry *filer_pb.Entry
  118. if lookupErr != nil {
  119. if lookupErr != filer_pb.ErrNotFound {
  120. return lookupErr
  121. }
  122. } else {
  123. existingEntry = lookupResponse.Entry
  124. }
  125. if existingEntry == nil {
  126. _, createErr := client.CreateEntry(ctx, &filer_pb.CreateEntryRequest{
  127. Directory: localDir,
  128. Entry: &filer_pb.Entry{
  129. Name: name,
  130. IsDirectory: isDirectory,
  131. Attributes: &filer_pb.FuseAttributes{
  132. FileSize: uint64(remoteEntry.Size),
  133. Mtime: remoteEntry.LastModifiedAt,
  134. FileMode: uint32(0644),
  135. },
  136. RemoteEntry: remoteEntry,
  137. },
  138. })
  139. return createErr
  140. } else {
  141. if existingEntry.RemoteEntry == nil || existingEntry.RemoteEntry.ETag != remoteEntry.ETag {
  142. existingEntry.RemoteEntry = remoteEntry
  143. existingEntry.Attributes.FileSize = uint64(remoteEntry.Size)
  144. existingEntry.Attributes.Mtime = remoteEntry.LastModifiedAt
  145. _, updateErr := client.UpdateEntry(ctx, &filer_pb.UpdateEntryRequest{
  146. Directory: localDir,
  147. Entry: existingEntry,
  148. })
  149. return updateErr
  150. }
  151. }
  152. return nil
  153. })
  154. return err
  155. })
  156. if err != nil {
  157. return err
  158. }
  159. return nil
  160. }
  161. func (c *commandRemoteMount) saveMountMapping(commandEnv *CommandEnv, writer io.Writer, dir string, remoteStorageLocation *filer_pb.RemoteStorageLocation) (err error) {
  162. // read current mapping
  163. var oldContent, newContent []byte
  164. err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  165. oldContent, err = filer.ReadInsideFiler(client, filer.DirectoryEtcRemote, filer.REMOTE_STORAGE_MOUNT_FILE)
  166. return err
  167. })
  168. if err != nil {
  169. if err != filer_pb.ErrNotFound {
  170. return fmt.Errorf("read existing mapping: %v", err)
  171. }
  172. }
  173. // add new mapping
  174. newContent, err = filer.AddRemoteStorageMapping(oldContent, dir, remoteStorageLocation)
  175. if err != nil {
  176. return fmt.Errorf("add mapping %s~%s: %v", dir, remoteStorageLocation, err)
  177. }
  178. // save back
  179. err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  180. return filer.SaveInsideFiler(client, filer.DirectoryEtcRemote, filer.REMOTE_STORAGE_MOUNT_FILE, newContent)
  181. })
  182. if err != nil {
  183. return fmt.Errorf("save mapping: %v", err)
  184. }
  185. return nil
  186. }