handlers.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package replication
  2. /*
  3. Replication endpoints.
  4. Node-facing (signed):
  5. POST /cluster/acn/repl/pull leader -> worker: make this copy
  6. POST /cluster/acn/repl/lease worker -> leader: still copying
  7. POST /cluster/acn/repl/done worker -> leader: result
  8. POST /cluster/acn/repl/plan any -> leader: run the planner now
  9. Admin (/system/cluster/repl/* and volume evacuation, admin only).
  10. */
  11. import (
  12. "encoding/json"
  13. "net/http"
  14. "imuslab.com/arozos/mod/cluster/acn"
  15. "imuslab.com/arozos/mod/utils"
  16. )
  17. const (
  18. pathPull = acn.BasePath + "/repl/pull"
  19. pathLease = acn.BasePath + "/repl/lease"
  20. pathDone = acn.BasePath + "/repl/done"
  21. pathPlan = acn.BasePath + "/repl/plan"
  22. )
  23. // TaskReport is what a worker sends back to the leader.
  24. type TaskReport struct {
  25. TaskID string `json:"taskId"`
  26. OK bool `json:"ok"`
  27. Error string `json:"error,omitempty"`
  28. }
  29. func (r *Manager) registerACNHandlers() {
  30. srv := r.m.Server()
  31. srv.HandleFunc(pathPull, r.handlePull)
  32. srv.HandleFunc(pathLease, r.handleLease)
  33. srv.HandleFunc(pathDone, r.handleDone)
  34. srv.HandleFunc(pathPlan, r.handlePlan)
  35. }
  36. func (r *Manager) handlePull(w http.ResponseWriter, req *http.Request, sender *acn.SignedIdentity, body []byte) {
  37. if sender.NodeID != r.meta.Leader() {
  38. acn.WriteError(w, http.StatusForbidden, "only the metadata leader assigns replication tasks")
  39. return
  40. }
  41. var t Task
  42. if err := json.Unmarshal(body, &t); err != nil || t.ID == "" || t.FileID == "" {
  43. acn.WriteError(w, http.StatusBadRequest, "invalid task")
  44. return
  45. }
  46. if t.TargetNode != r.m.NodeID() {
  47. acn.WriteError(w, http.StatusBadRequest, "task is for another node")
  48. return
  49. }
  50. go r.runTask(t)
  51. acn.WriteJSON(w, map[string]bool{"ok": true})
  52. }
  53. func (r *Manager) handleLease(w http.ResponseWriter, req *http.Request, sender *acn.SignedIdentity, body []byte) {
  54. if !r.meta.IsLeader() {
  55. acn.WriteError(w, http.StatusConflict, ErrNotLeader.Error())
  56. return
  57. }
  58. var rep TaskReport
  59. if err := json.Unmarshal(body, &rep); err != nil {
  60. acn.WriteError(w, http.StatusBadRequest, "invalid report")
  61. return
  62. }
  63. acn.WriteJSON(w, map[string]bool{"ok": r.renewLease(rep.TaskID)})
  64. }
  65. func (r *Manager) handleDone(w http.ResponseWriter, req *http.Request, sender *acn.SignedIdentity, body []byte) {
  66. if !r.meta.IsLeader() {
  67. acn.WriteError(w, http.StatusConflict, ErrNotLeader.Error())
  68. return
  69. }
  70. var rep TaskReport
  71. if err := json.Unmarshal(body, &rep); err != nil {
  72. acn.WriteError(w, http.StatusBadRequest, "invalid report")
  73. return
  74. }
  75. r.mu.Lock()
  76. r.finishLocked(rep.TaskID, rep.OK, rep.Error)
  77. r.mu.Unlock()
  78. acn.WriteJSON(w, map[string]bool{"ok": true})
  79. }
  80. func (r *Manager) handlePlan(w http.ResponseWriter, req *http.Request, sender *acn.SignedIdentity, body []byte) {
  81. if !r.meta.IsLeader() {
  82. acn.WriteError(w, http.StatusConflict, ErrNotLeader.Error())
  83. return
  84. }
  85. acn.WriteJSON(w, r.plan())
  86. }
  87. /*
  88. Admin handlers
  89. */
  90. func sendJSON(w http.ResponseWriter, v interface{}) {
  91. js, _ := json.Marshal(v)
  92. utils.SendJSONResponse(w, string(js))
  93. }
  94. // HandleStatus returns the replication picture.
  95. func (r *Manager) HandleStatus(w http.ResponseWriter, req *http.Request) {
  96. sendJSON(w, r.Status())
  97. }
  98. // HandlePlan runs the planner now (forwarded to the leader when needed).
  99. func (r *Manager) HandlePlan(w http.ResponseWriter, req *http.Request) {
  100. res, err := r.PlanNow()
  101. if err != nil {
  102. utils.SendErrorResponse(w, err.Error())
  103. return
  104. }
  105. sendJSON(w, res)
  106. }
  107. // HandleVerify re-checksums this node's copies now. Optional budget in MB.
  108. func (r *Manager) HandleVerify(w http.ResponseWriter, req *http.Request) {
  109. var budget int64
  110. if mb, err := utils.PostInt(req, "budget"); err == nil && mb > 0 {
  111. budget = int64(mb) << 20
  112. }
  113. sendJSON(w, r.st.VerifyLocalCopies(budget))
  114. }
  115. // HandleEvacuate starts moving everything off a local volume.
  116. func (r *Manager) HandleEvacuate(w http.ResponseWriter, req *http.Request) {
  117. id, err := utils.PostPara(req, "id")
  118. if err != nil {
  119. utils.SendErrorResponse(w, "volume id required")
  120. return
  121. }
  122. if err := r.st.StartEvacuation(id); err != nil {
  123. utils.SendErrorResponse(w, err.Error())
  124. return
  125. }
  126. go r.PlanNow()
  127. sendJSON(w, r.st.EvacuationStatus(id))
  128. }
  129. // HandleEvacuateCancel makes an evacuating volume writable again.
  130. func (r *Manager) HandleEvacuateCancel(w http.ResponseWriter, req *http.Request) {
  131. id, err := utils.PostPara(req, "id")
  132. if err != nil {
  133. utils.SendErrorResponse(w, "volume id required")
  134. return
  135. }
  136. if err := r.st.CancelEvacuation(id); err != nil {
  137. utils.SendErrorResponse(w, err.Error())
  138. return
  139. }
  140. utils.SendOK(w)
  141. }
  142. // HandleEvacuateStatus reports evacuation progress: id=
  143. func (r *Manager) HandleEvacuateStatus(w http.ResponseWriter, req *http.Request) {
  144. id, err := utils.GetPara(req, "id")
  145. if err != nil {
  146. utils.SendErrorResponse(w, "volume id required")
  147. return
  148. }
  149. sendJSON(w, r.st.EvacuationStatus(id))
  150. }
  151. // RegisterAdminRoutes mounts the admin handlers.
  152. func (r *Manager) RegisterAdminRoutes(register func(pattern string, handler func(http.ResponseWriter, *http.Request))) {
  153. register("/system/cluster/repl/status", r.HandleStatus)
  154. register("/system/cluster/repl/plan", r.HandlePlan)
  155. register("/system/cluster/repl/verify", r.HandleVerify)
  156. register("/system/cluster/storage/volume/evacuate", r.HandleEvacuate)
  157. register("/system/cluster/storage/volume/evacuate/cancel", r.HandleEvacuateCancel)
  158. register("/system/cluster/storage/volume/evacuate/status", r.HandleEvacuateStatus)
  159. }