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
7.5 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
  1. package shell
  2. import (
  3. "flag"
  4. "fmt"
  5. "github.com/chrislusf/seaweedfs/weed/pb/master_pb"
  6. "github.com/chrislusf/seaweedfs/weed/storage/types"
  7. "github.com/chrislusf/seaweedfs/weed/wdclient"
  8. "io"
  9. "path/filepath"
  10. "sync"
  11. "time"
  12. "github.com/chrislusf/seaweedfs/weed/storage/needle"
  13. )
  14. func init() {
  15. Commands = append(Commands, &commandVolumeTierMove{})
  16. }
  17. type commandVolumeTierMove struct {
  18. activeServers map[string]struct{}
  19. activeServersLock sync.Mutex
  20. activeServersCond *sync.Cond
  21. }
  22. func (c *commandVolumeTierMove) Name() string {
  23. return "volume.tier.move"
  24. }
  25. func (c *commandVolumeTierMove) Help() string {
  26. return `change a volume from one disk type to another
  27. volume.tier.move -fromDiskType=hdd -toDiskType=ssd [-collectionPattern=""] [-fullPercent=95] [-quietFor=1h]
  28. Even if the volume is replicated, only one replica will be changed and the rest replicas will be dropped.
  29. So "volume.fix.replication" and "volume.balance" should be followed.
  30. `
  31. }
  32. func (c *commandVolumeTierMove) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  33. c.activeServers = make(map[string]struct{})
  34. c.activeServersCond = sync.NewCond(new(sync.Mutex))
  35. if err = commandEnv.confirmIsLocked(); err != nil {
  36. return
  37. }
  38. tierCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  39. collectionPattern := tierCommand.String("collectionPattern", "", "match with wildcard characters '*' and '?'")
  40. fullPercentage := tierCommand.Float64("fullPercent", 95, "the volume reaches the percentage of max volume size")
  41. quietPeriod := tierCommand.Duration("quietFor", 24*time.Hour, "select volumes without no writes for this period")
  42. source := tierCommand.String("fromDiskType", "", "the source disk type")
  43. target := tierCommand.String("toDiskType", "", "the target disk type")
  44. applyChange := tierCommand.Bool("force", false, "actually apply the changes")
  45. if err = tierCommand.Parse(args); err != nil {
  46. return nil
  47. }
  48. fromDiskType := types.ToDiskType(*source)
  49. toDiskType := types.ToDiskType(*target)
  50. if fromDiskType == toDiskType {
  51. return fmt.Errorf("source tier %s is the same as target tier %s", fromDiskType, toDiskType)
  52. }
  53. // collect topology information
  54. topologyInfo, volumeSizeLimitMb, err := collectTopologyInfo(commandEnv)
  55. if err != nil {
  56. return err
  57. }
  58. // collect all volumes that should change
  59. volumeIds, err := collectVolumeIdsForTierChange(commandEnv, topologyInfo, volumeSizeLimitMb, fromDiskType, *collectionPattern, *fullPercentage, *quietPeriod)
  60. if err != nil {
  61. return err
  62. }
  63. fmt.Printf("tier move volumes: %v\n", volumeIds)
  64. _, allLocations := collectVolumeReplicaLocations(topologyInfo)
  65. for _, vid := range volumeIds {
  66. if err = c.doVolumeTierMove(commandEnv, writer, vid, toDiskType, allLocations, *applyChange); err != nil {
  67. fmt.Printf("tier move volume %d: %v\n", vid, err)
  68. }
  69. }
  70. return nil
  71. }
  72. func isOneOf(server string, locations []wdclient.Location) bool {
  73. for _, loc := range locations {
  74. if server == loc.Url {
  75. return true
  76. }
  77. }
  78. return false
  79. }
  80. func (c *commandVolumeTierMove) doVolumeTierMove(commandEnv *CommandEnv, writer io.Writer, vid needle.VolumeId, toDiskType types.DiskType, allLocations []location, applyChanges bool) (err error) {
  81. // find volume location
  82. locations, found := commandEnv.MasterClient.GetLocations(uint32(vid))
  83. if !found {
  84. return fmt.Errorf("volume %d not found", vid)
  85. }
  86. // find one server with the most empty volume slots with target disk type
  87. hasFoundTarget := false
  88. keepDataNodesSorted(allLocations, toDiskType)
  89. fn := capacityByFreeVolumeCount(toDiskType)
  90. wg := sync.WaitGroup{}
  91. for _, dst := range allLocations {
  92. if fn(dst.dataNode) > 0 && !hasFoundTarget {
  93. // ask the volume server to replicate the volume
  94. if isOneOf(dst.dataNode.Id, locations) {
  95. continue
  96. }
  97. sourceVolumeServer := ""
  98. for _, loc := range locations {
  99. if loc.Url != dst.dataNode.Id {
  100. sourceVolumeServer = loc.Url
  101. }
  102. }
  103. if sourceVolumeServer == "" {
  104. continue
  105. }
  106. fmt.Fprintf(writer, "moving volume %d from %s to %s with disk type %s ...\n", vid, sourceVolumeServer, dst.dataNode.Id, toDiskType.ReadableString())
  107. hasFoundTarget = true
  108. if !applyChanges {
  109. // adjust volume count
  110. dst.dataNode.DiskInfos[string(toDiskType)].VolumeCount++
  111. break
  112. }
  113. c.activeServersCond.L.Lock()
  114. _, isSourceActive := c.activeServers[sourceVolumeServer]
  115. _, isDestActive := c.activeServers[dst.dataNode.Id]
  116. for isSourceActive || isDestActive {
  117. c.activeServersCond.Wait()
  118. _, isSourceActive = c.activeServers[sourceVolumeServer]
  119. _, isDestActive = c.activeServers[dst.dataNode.Id]
  120. }
  121. c.activeServers[sourceVolumeServer] = struct{}{}
  122. c.activeServers[dst.dataNode.Id] = struct{}{}
  123. c.activeServersCond.L.Unlock()
  124. wg.Add(1)
  125. go func(dst location) {
  126. if err := c.doMoveOneVolume(commandEnv, writer, vid, toDiskType, locations, sourceVolumeServer, dst); err != nil {
  127. fmt.Fprintf(writer, "move volume %d %s => %s: %v\n", vid, sourceVolumeServer, dst.dataNode.Id, err)
  128. }
  129. delete(c.activeServers, sourceVolumeServer)
  130. delete(c.activeServers, dst.dataNode.Id)
  131. c.activeServersCond.Signal()
  132. wg.Done()
  133. }(dst)
  134. }
  135. }
  136. wg.Wait()
  137. if !hasFoundTarget {
  138. fmt.Fprintf(writer, "can not find disk type %s for volume %d\n", toDiskType.ReadableString(), vid)
  139. }
  140. return nil
  141. }
  142. func (c *commandVolumeTierMove) doMoveOneVolume(commandEnv *CommandEnv, writer io.Writer, vid needle.VolumeId, toDiskType types.DiskType, locations []wdclient.Location, sourceVolumeServer string, dst location) (err error) {
  143. // mark all replicas as read only
  144. if err = markVolumeReadonly(commandEnv.option.GrpcDialOption, vid, locations); err != nil {
  145. return fmt.Errorf("mark volume %d as readonly on %s: %v", vid, locations[0].Url, err)
  146. }
  147. if err = LiveMoveVolume(commandEnv.option.GrpcDialOption, writer, vid, sourceVolumeServer, dst.dataNode.Id, 5*time.Second, toDiskType.ReadableString(), true); err != nil {
  148. return fmt.Errorf("move volume %d %s => %s : %v", vid, locations[0].Url, dst.dataNode.Id, err)
  149. }
  150. // adjust volume count
  151. dst.dataNode.DiskInfos[string(toDiskType)].VolumeCount++
  152. // remove the remaining replicas
  153. for _, loc := range locations {
  154. if loc.Url != dst.dataNode.Id && loc.Url != sourceVolumeServer {
  155. if err = deleteVolume(commandEnv.option.GrpcDialOption, vid, loc.Url); err != nil {
  156. fmt.Fprintf(writer, "failed to delete volume %d on %s: %v\n", vid, loc.Url, err)
  157. }
  158. }
  159. }
  160. return nil
  161. }
  162. func collectVolumeIdsForTierChange(commandEnv *CommandEnv, topologyInfo *master_pb.TopologyInfo, volumeSizeLimitMb uint64, sourceTier types.DiskType, collectionPattern string, fullPercentage float64, quietPeriod time.Duration) (vids []needle.VolumeId, err error) {
  163. quietSeconds := int64(quietPeriod / time.Second)
  164. nowUnixSeconds := time.Now().Unix()
  165. fmt.Printf("collect %s volumes quiet for: %d seconds\n", sourceTier, quietSeconds)
  166. vidMap := make(map[uint32]bool)
  167. eachDataNode(topologyInfo, func(dc string, rack RackId, dn *master_pb.DataNodeInfo) {
  168. for _, diskInfo := range dn.DiskInfos {
  169. for _, v := range diskInfo.VolumeInfos {
  170. // check collection name pattern
  171. if collectionPattern != "" {
  172. matched, err := filepath.Match(collectionPattern, v.Collection)
  173. if err != nil {
  174. return
  175. }
  176. if !matched {
  177. continue
  178. }
  179. }
  180. if v.ModifiedAtSecond+quietSeconds < nowUnixSeconds && types.ToDiskType(v.DiskType) == sourceTier {
  181. if float64(v.Size) > fullPercentage/100*float64(volumeSizeLimitMb)*1024*1024 {
  182. vidMap[v.Id] = true
  183. }
  184. }
  185. }
  186. }
  187. })
  188. for vid := range vidMap {
  189. vids = append(vids, needle.VolumeId(vid))
  190. }
  191. return
  192. }