command_volume_move.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package shell
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "log"
  8. "time"
  9. "github.com/chrislusf/seaweedfs/weed/operation"
  10. "github.com/chrislusf/seaweedfs/weed/pb/volume_server_pb"
  11. "github.com/chrislusf/seaweedfs/weed/storage/needle"
  12. "google.golang.org/grpc"
  13. )
  14. func init() {
  15. Commands = append(Commands, &commandVolumeMove{})
  16. }
  17. type commandVolumeMove struct {
  18. }
  19. func (c *commandVolumeMove) Name() string {
  20. return "volume.move"
  21. }
  22. func (c *commandVolumeMove) Help() string {
  23. return `move a live volume from one volume server to another volume server
  24. volume.move -source <source volume server host:port> -target <target volume server host:port> -volumeId <volume id>
  25. This command move a live volume from one volume server to another volume server. Here are the steps:
  26. 1. This command asks the target volume server to copy the source volume from source volume server, remember the last entry's timestamp.
  27. 2. This command asks the target volume server to mount the new volume
  28. Now the master will mark this volume id as readonly.
  29. 3. This command asks the target volume server to tail the source volume for updates after the timestamp, for 1 minutes to drain the requests.
  30. 4. This command asks the source volume server to unmount the source volume
  31. Now the master will mark this volume id as writable.
  32. 5. This command asks the source volume server to delete the source volume
  33. `
  34. }
  35. func (c *commandVolumeMove) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  36. if err = commandEnv.confirmIsLocked(); err != nil {
  37. return
  38. }
  39. volMoveCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  40. volumeIdInt := volMoveCommand.Int("volumeId", 0, "the volume id")
  41. sourceNodeStr := volMoveCommand.String("source", "", "the source volume server <host>:<port>")
  42. targetNodeStr := volMoveCommand.String("target", "", "the target volume server <host>:<port>")
  43. if err = volMoveCommand.Parse(args); err != nil {
  44. return nil
  45. }
  46. sourceVolumeServer, targetVolumeServer := *sourceNodeStr, *targetNodeStr
  47. volumeId := needle.VolumeId(*volumeIdInt)
  48. if sourceVolumeServer == targetVolumeServer {
  49. return fmt.Errorf("source and target volume servers are the same!")
  50. }
  51. return LiveMoveVolume(commandEnv.option.GrpcDialOption, volumeId, sourceVolumeServer, targetVolumeServer, 5*time.Second)
  52. }
  53. // LiveMoveVolume moves one volume from one source volume server to one target volume server, with idleTimeout to drain the incoming requests.
  54. func LiveMoveVolume(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, sourceVolumeServer, targetVolumeServer string, idleTimeout time.Duration) (err error) {
  55. log.Printf("copying volume %d from %s to %s", volumeId, sourceVolumeServer, targetVolumeServer)
  56. lastAppendAtNs, err := copyVolume(grpcDialOption, volumeId, sourceVolumeServer, targetVolumeServer)
  57. if err != nil {
  58. return fmt.Errorf("copy volume %d from %s to %s: %v", volumeId, sourceVolumeServer, targetVolumeServer, err)
  59. }
  60. log.Printf("tailing volume %d from %s to %s", volumeId, sourceVolumeServer, targetVolumeServer)
  61. if err = tailVolume(grpcDialOption, volumeId, sourceVolumeServer, targetVolumeServer, lastAppendAtNs, idleTimeout); err != nil {
  62. return fmt.Errorf("tail volume %d from %s to %s: %v", volumeId, sourceVolumeServer, targetVolumeServer, err)
  63. }
  64. log.Printf("deleting volume %d from %s", volumeId, sourceVolumeServer)
  65. if err = deleteVolume(grpcDialOption, volumeId, sourceVolumeServer); err != nil {
  66. return fmt.Errorf("delete volume %d from %s: %v", volumeId, sourceVolumeServer, err)
  67. }
  68. log.Printf("moved volume %d from %s to %s", volumeId, sourceVolumeServer, targetVolumeServer)
  69. return nil
  70. }
  71. func copyVolume(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, sourceVolumeServer, targetVolumeServer string) (lastAppendAtNs uint64, err error) {
  72. // check to see if the volume is already read-only and if its not then we need
  73. // to mark it as read-only and then before we return we need to undo what we
  74. // did
  75. var shouldMarkWritable bool
  76. defer func() {
  77. if !shouldMarkWritable {
  78. return
  79. }
  80. clientErr := operation.WithVolumeServerClient(sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  81. _, writableErr := volumeServerClient.VolumeMarkWritable(context.Background(), &volume_server_pb.VolumeMarkWritableRequest{
  82. VolumeId: uint32(volumeId),
  83. })
  84. return writableErr
  85. })
  86. if clientErr != nil {
  87. log.Printf("failed to mark volume %d as writable after copy from %s: %v", volumeId, sourceVolumeServer, clientErr)
  88. }
  89. }()
  90. err = operation.WithVolumeServerClient(sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  91. resp, statusErr := volumeServerClient.VolumeStatus(context.Background(), &volume_server_pb.VolumeStatusRequest{
  92. VolumeId: uint32(volumeId),
  93. })
  94. if statusErr == nil && !resp.IsReadOnly {
  95. shouldMarkWritable = true
  96. _, readonlyErr := volumeServerClient.VolumeMarkReadonly(context.Background(), &volume_server_pb.VolumeMarkReadonlyRequest{
  97. VolumeId: uint32(volumeId),
  98. })
  99. return readonlyErr
  100. }
  101. return statusErr
  102. })
  103. if err != nil {
  104. return
  105. }
  106. err = operation.WithVolumeServerClient(targetVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  107. resp, replicateErr := volumeServerClient.VolumeCopy(context.Background(), &volume_server_pb.VolumeCopyRequest{
  108. VolumeId: uint32(volumeId),
  109. SourceDataNode: sourceVolumeServer,
  110. })
  111. if replicateErr == nil {
  112. lastAppendAtNs = resp.LastAppendAtNs
  113. }
  114. return replicateErr
  115. })
  116. return
  117. }
  118. func tailVolume(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, sourceVolumeServer, targetVolumeServer string, lastAppendAtNs uint64, idleTimeout time.Duration) (err error) {
  119. return operation.WithVolumeServerClient(targetVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  120. _, replicateErr := volumeServerClient.VolumeTailReceiver(context.Background(), &volume_server_pb.VolumeTailReceiverRequest{
  121. VolumeId: uint32(volumeId),
  122. SinceNs: lastAppendAtNs,
  123. IdleTimeoutSeconds: uint32(idleTimeout.Seconds()),
  124. SourceVolumeServer: sourceVolumeServer,
  125. })
  126. return replicateErr
  127. })
  128. }
  129. func deleteVolume(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, sourceVolumeServer string) (err error) {
  130. return operation.WithVolumeServerClient(sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  131. _, deleteErr := volumeServerClient.VolumeDelete(context.Background(), &volume_server_pb.VolumeDeleteRequest{
  132. VolumeId: uint32(volumeId),
  133. })
  134. return deleteErr
  135. })
  136. }
  137. func markVolumeWritable(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, sourceVolumeServer string, writable bool) (err error) {
  138. return operation.WithVolumeServerClient(sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  139. if writable {
  140. _, err = volumeServerClient.VolumeMarkWritable(context.Background(), &volume_server_pb.VolumeMarkWritableRequest{
  141. VolumeId: uint32(volumeId),
  142. })
  143. } else {
  144. _, err = volumeServerClient.VolumeMarkReadonly(context.Background(), &volume_server_pb.VolumeMarkReadonlyRequest{
  145. VolumeId: uint32(volumeId),
  146. })
  147. }
  148. return err
  149. })
  150. }