tunasync/manager/server.go

187 lines
4.6 KiB
Go

package manager
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
. "github.com/tuna/tunasync/internal"
)
const (
_errorKey = "error"
_infoKey = "message"
)
type managerServer struct {
*gin.Engine
adapter dbAdapter
}
// listAllJobs repond with all jobs of specified workers
func (s *managerServer) listAllJobs(c *gin.Context) {
mirrorStatusList, err := s.adapter.ListAllMirrorStatus()
if err != nil {
err := fmt.Errorf("failed to list all mirror status: %s",
err.Error(),
)
c.Error(err)
s.returnErrJSON(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, mirrorStatusList)
}
// listWrokers respond with informations of all the workers
func (s *managerServer) listWorkers(c *gin.Context) {
var workerInfos []WorkerInfoMsg
workers, err := s.adapter.ListWorkers()
if err != nil {
err := fmt.Errorf("failed to list workers: %s",
err.Error(),
)
c.Error(err)
s.returnErrJSON(c, http.StatusInternalServerError, err)
return
}
for _, w := range workers {
workerInfos = append(workerInfos,
WorkerInfoMsg{w.ID, w.LastOnline})
}
c.JSON(http.StatusOK, workerInfos)
}
// registerWorker register an newly-online worker
func (s *managerServer) registerWorker(c *gin.Context) {
var _worker workerStatus
c.BindJSON(&_worker)
newWorker, err := s.adapter.CreateWorker(_worker)
if err != nil {
err := fmt.Errorf("failed to register worker: %s",
err.Error(),
)
c.Error(err)
s.returnErrJSON(c, http.StatusInternalServerError, err)
return
}
// create workerCmd channel for this worker
c.JSON(http.StatusOK, newWorker)
}
// listJobsOfWorker respond with all the jobs of the specified worker
func (s *managerServer) listJobsOfWorker(c *gin.Context) {
workerID := c.Param("id")
mirrorStatusList, err := s.adapter.ListMirrorStatus(workerID)
if err != nil {
err := fmt.Errorf("failed to list jobs of worker %s: %s",
workerID, err.Error(),
)
c.Error(err)
s.returnErrJSON(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, mirrorStatusList)
}
func (s *managerServer) returnErrJSON(c *gin.Context, code int, err error) {
c.JSON(code, gin.H{
_errorKey: err.Error(),
})
}
func (s *managerServer) updateJobOfWorker(c *gin.Context) {
workerID := c.Param("id")
var status mirrorStatus
c.BindJSON(&status)
mirrorName := status.Name
newStatus, err := s.adapter.UpdateMirrorStatus(workerID, mirrorName, status)
if err != nil {
err := fmt.Errorf("failed to update job %s of worker %s: %s",
mirrorName, workerID, err.Error(),
)
c.Error(err)
s.returnErrJSON(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, newStatus)
}
func (s *managerServer) handleClientCmd(c *gin.Context) {
var clientCmd ClientCmd
c.BindJSON(&clientCmd)
workerID := clientCmd.WorkerID
if workerID == "" {
// TODO: decide which worker should do this mirror when WorkerID is null string
logger.Error("handleClientCmd case workerID == \" \" not implemented yet")
c.AbortWithStatus(http.StatusInternalServerError)
return
}
w, err := s.adapter.GetWorker(workerID)
if err != nil {
err := fmt.Errorf("worker %s is not registered yet", workerID)
s.returnErrJSON(c, http.StatusBadRequest, err)
return
}
workerURL := w.URL
// parse client cmd into worker cmd
workerCmd := WorkerCmd{
Cmd: clientCmd.Cmd,
MirrorID: clientCmd.MirrorID,
Args: clientCmd.Args,
}
// post command to worker
_, err = postJSON(workerURL, workerCmd)
if err != nil {
err := fmt.Errorf("post command to worker %s(%s) fail: %s", workerID, workerURL, err.Error())
c.Error(err)
s.returnErrJSON(c, http.StatusInternalServerError, err)
return
}
// TODO: check response for success
c.JSON(http.StatusOK, gin.H{_infoKey: "successfully send command to worker " + workerID})
}
func (s *managerServer) setDBAdapter(adapter dbAdapter) {
s.adapter = adapter
}
func makeHTTPServer(debug bool) *managerServer {
// create gin engine
if !debug {
gin.SetMode(gin.ReleaseMode)
}
s := &managerServer{
gin.Default(),
nil,
}
// common log middleware
s.Use(contextErrorLogger)
s.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{_infoKey: "pong"})
})
// list jobs, status page
s.GET("/jobs", s.listAllJobs)
// list workers
s.GET("/workers", s.listWorkers)
// worker online
s.POST("/workers", s.registerWorker)
// workerID should be valid in this route group
workerValidateGroup := s.Group("/workers", s.workerIDValidator)
// get job list
workerValidateGroup.GET(":id/jobs", s.listJobsOfWorker)
// post job status
workerValidateGroup.POST(":id/jobs/:job", s.updateJobOfWorker)
// for tunasynctl to post commands
s.POST("/cmd", s.handleClientCmd)
return s
}