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.

242 lines
9.7 KiB

3 months ago
6 years ago
6 years ago
3 years ago
6 years ago
  1. package shell
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "log"
  8. "time"
  9. "github.com/seaweedfs/seaweedfs/weed/pb"
  10. "github.com/seaweedfs/seaweedfs/weed/wdclient"
  11. "github.com/seaweedfs/seaweedfs/weed/operation"
  12. "github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
  13. "github.com/seaweedfs/seaweedfs/weed/storage/needle"
  14. "github.com/seaweedfs/seaweedfs/weed/util"
  15. "google.golang.org/grpc"
  16. )
  17. func init() {
  18. Commands = append(Commands, &commandVolumeMove{})
  19. }
  20. type commandVolumeMove struct {
  21. }
  22. func (c *commandVolumeMove) Name() string {
  23. return "volume.move"
  24. }
  25. func (c *commandVolumeMove) Help() string {
  26. return `move a live volume from one volume server to another volume server
  27. volume.move -source <source volume server host:port> -target <target volume server host:port> -volumeId <volume id>
  28. volume.move -source <source volume server host:port> -target <target volume server host:port> -volumeId <volume id> -disk [hdd|ssd|<tag>]
  29. This command move a live volume from one volume server to another volume server. Here are the steps:
  30. 1. This command asks the target volume server to copy the source volume from source volume server, remember the last entry's timestamp.
  31. 2. This command asks the target volume server to mount the new volume
  32. Now the master will mark this volume id as readonly.
  33. 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.
  34. 4. This command asks the source volume server to unmount the source volume
  35. Now the master will mark this volume id as writable.
  36. 5. This command asks the source volume server to delete the source volume
  37. The option "-disk [hdd|ssd|<tag>]" can be used to change the volume disk type.
  38. `
  39. }
  40. func (c *commandVolumeMove) HasTag(CommandTag) bool {
  41. return false
  42. }
  43. func (c *commandVolumeMove) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  44. volMoveCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  45. volumeIdInt := volMoveCommand.Int("volumeId", 0, "the volume id")
  46. sourceNodeStr := volMoveCommand.String("source", "", "the source volume server <host>:<port>")
  47. targetNodeStr := volMoveCommand.String("target", "", "the target volume server <host>:<port>")
  48. diskTypeStr := volMoveCommand.String("disk", "", "[hdd|ssd|<tag>] hard drive or solid state drive or any tag")
  49. ioBytePerSecond := volMoveCommand.Int64("ioBytePerSecond", 0, "limit the speed of move")
  50. noLock := volMoveCommand.Bool("noLock", false, "do not lock the admin shell at one's own risk")
  51. if err = volMoveCommand.Parse(args); err != nil {
  52. return nil
  53. }
  54. if *noLock {
  55. commandEnv.noLock = true
  56. } else {
  57. if err = commandEnv.confirmIsLocked(args); err != nil {
  58. return
  59. }
  60. }
  61. sourceVolumeServer, targetVolumeServer := pb.ServerAddress(*sourceNodeStr), pb.ServerAddress(*targetNodeStr)
  62. volumeId := needle.VolumeId(*volumeIdInt)
  63. if sourceVolumeServer == targetVolumeServer {
  64. return fmt.Errorf("source and target volume servers are the same!")
  65. }
  66. return LiveMoveVolume(commandEnv.option.GrpcDialOption, writer, volumeId, sourceVolumeServer, targetVolumeServer, 5*time.Second, *diskTypeStr, *ioBytePerSecond, false)
  67. }
  68. // LiveMoveVolume moves one volume from one source volume server to one target volume server, with idleTimeout to drain the incoming requests.
  69. func LiveMoveVolume(grpcDialOption grpc.DialOption, writer io.Writer, volumeId needle.VolumeId, sourceVolumeServer, targetVolumeServer pb.ServerAddress, idleTimeout time.Duration, diskType string, ioBytePerSecond int64, skipTailError bool) (err error) {
  70. log.Printf("copying volume %d from %s to %s", volumeId, sourceVolumeServer, targetVolumeServer)
  71. lastAppendAtNs, err := copyVolume(grpcDialOption, writer, volumeId, sourceVolumeServer, targetVolumeServer, diskType, ioBytePerSecond)
  72. if err != nil {
  73. return fmt.Errorf("copy volume %d from %s to %s: %v", volumeId, sourceVolumeServer, targetVolumeServer, err)
  74. }
  75. log.Printf("tailing volume %d from %s to %s", volumeId, sourceVolumeServer, targetVolumeServer)
  76. if err = tailVolume(grpcDialOption, volumeId, sourceVolumeServer, targetVolumeServer, lastAppendAtNs, idleTimeout); err != nil {
  77. if skipTailError {
  78. fmt.Fprintf(writer, "tail volume %d from %s to %s: %v\n", volumeId, sourceVolumeServer, targetVolumeServer, err)
  79. } else {
  80. return fmt.Errorf("tail volume %d from %s to %s: %v", volumeId, sourceVolumeServer, targetVolumeServer, err)
  81. }
  82. }
  83. log.Printf("deleting volume %d from %s", volumeId, sourceVolumeServer)
  84. if err = deleteVolume(grpcDialOption, volumeId, sourceVolumeServer, false); err != nil {
  85. return fmt.Errorf("delete volume %d from %s: %v", volumeId, sourceVolumeServer, err)
  86. }
  87. log.Printf("moved volume %d from %s to %s", volumeId, sourceVolumeServer, targetVolumeServer)
  88. return nil
  89. }
  90. func copyVolume(grpcDialOption grpc.DialOption, writer io.Writer, volumeId needle.VolumeId, sourceVolumeServer, targetVolumeServer pb.ServerAddress, diskType string, ioBytePerSecond int64) (lastAppendAtNs uint64, err error) {
  91. // check to see if the volume is already read-only and if its not then we need
  92. // to mark it as read-only and then before we return we need to undo what we
  93. // did
  94. var shouldMarkWritable bool
  95. defer func() {
  96. if !shouldMarkWritable {
  97. return
  98. }
  99. clientErr := operation.WithVolumeServerClient(false, sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  100. _, writableErr := volumeServerClient.VolumeMarkWritable(context.Background(), &volume_server_pb.VolumeMarkWritableRequest{
  101. VolumeId: uint32(volumeId),
  102. })
  103. return writableErr
  104. })
  105. if clientErr != nil {
  106. log.Printf("failed to mark volume %d as writable after copy from %s: %v", volumeId, sourceVolumeServer, clientErr)
  107. }
  108. }()
  109. err = operation.WithVolumeServerClient(false, sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  110. resp, statusErr := volumeServerClient.VolumeStatus(context.Background(), &volume_server_pb.VolumeStatusRequest{
  111. VolumeId: uint32(volumeId),
  112. })
  113. if statusErr == nil && !resp.IsReadOnly {
  114. shouldMarkWritable = true
  115. _, readonlyErr := volumeServerClient.VolumeMarkReadonly(context.Background(), &volume_server_pb.VolumeMarkReadonlyRequest{
  116. VolumeId: uint32(volumeId),
  117. Persist: false,
  118. })
  119. return readonlyErr
  120. }
  121. return statusErr
  122. })
  123. if err != nil {
  124. return
  125. }
  126. err = operation.WithVolumeServerClient(true, targetVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  127. stream, replicateErr := volumeServerClient.VolumeCopy(context.Background(), &volume_server_pb.VolumeCopyRequest{
  128. VolumeId: uint32(volumeId),
  129. SourceDataNode: string(sourceVolumeServer),
  130. DiskType: diskType,
  131. IoBytePerSecond: ioBytePerSecond,
  132. })
  133. if replicateErr != nil {
  134. return replicateErr
  135. }
  136. for {
  137. resp, recvErr := stream.Recv()
  138. if recvErr != nil {
  139. if recvErr == io.EOF {
  140. break
  141. } else {
  142. return recvErr
  143. }
  144. }
  145. if resp.LastAppendAtNs != 0 {
  146. lastAppendAtNs = resp.LastAppendAtNs
  147. } else {
  148. fmt.Fprintf(writer, "%s => %s volume %d processed %s\n", sourceVolumeServer, targetVolumeServer, volumeId, util.BytesToHumanReadable(uint64(resp.ProcessedBytes)))
  149. }
  150. }
  151. return nil
  152. })
  153. return
  154. }
  155. func tailVolume(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, sourceVolumeServer, targetVolumeServer pb.ServerAddress, lastAppendAtNs uint64, idleTimeout time.Duration) (err error) {
  156. return operation.WithVolumeServerClient(true, targetVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  157. _, replicateErr := volumeServerClient.VolumeTailReceiver(context.Background(), &volume_server_pb.VolumeTailReceiverRequest{
  158. VolumeId: uint32(volumeId),
  159. SinceNs: lastAppendAtNs,
  160. IdleTimeoutSeconds: uint32(idleTimeout.Seconds()),
  161. SourceVolumeServer: string(sourceVolumeServer),
  162. })
  163. return replicateErr
  164. })
  165. }
  166. func deleteVolume(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, sourceVolumeServer pb.ServerAddress, onlyEmpty bool) (err error) {
  167. return operation.WithVolumeServerClient(false, sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  168. _, deleteErr := volumeServerClient.VolumeDelete(context.Background(), &volume_server_pb.VolumeDeleteRequest{
  169. VolumeId: uint32(volumeId),
  170. OnlyEmpty: onlyEmpty,
  171. })
  172. return deleteErr
  173. })
  174. }
  175. func markVolumeWritable(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, sourceVolumeServer pb.ServerAddress, writable, persist bool) (err error) {
  176. return operation.WithVolumeServerClient(false, sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  177. if writable {
  178. _, err = volumeServerClient.VolumeMarkWritable(context.Background(), &volume_server_pb.VolumeMarkWritableRequest{
  179. VolumeId: uint32(volumeId),
  180. })
  181. } else {
  182. _, err = volumeServerClient.VolumeMarkReadonly(context.Background(), &volume_server_pb.VolumeMarkReadonlyRequest{
  183. VolumeId: uint32(volumeId),
  184. Persist: persist,
  185. })
  186. }
  187. return err
  188. })
  189. }
  190. func markVolumeReplicaWritable(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, location wdclient.Location, writable, persist bool) error {
  191. fmt.Printf("markVolumeReadonly %d on %s ...\n", volumeId, location.Url)
  192. return markVolumeWritable(grpcDialOption, volumeId, location.ServerAddress(), writable, persist)
  193. }
  194. func markVolumeReplicasWritable(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, locations []wdclient.Location, writable, persist bool) error {
  195. for _, location := range locations {
  196. if err := markVolumeReplicaWritable(grpcDialOption, volumeId, location, writable, persist); err != nil {
  197. return err
  198. }
  199. }
  200. return nil
  201. }