worker.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. package worker
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "errors"
  6. "fmt"
  7. "html/template"
  8. "net/http"
  9. "path/filepath"
  10. "time"
  11. "github.com/gin-gonic/gin"
  12. . "github.com/tuna/tunasync/internal"
  13. )
  14. var tunasyncWorker *Worker
  15. // A Worker is a instance of tunasync worker
  16. type Worker struct {
  17. cfg *Config
  18. providers map[string]mirrorProvider
  19. jobs map[string]*mirrorJob
  20. managerChan chan jobMessage
  21. semaphore chan empty
  22. schedule *scheduleQueue
  23. httpServer *gin.Engine
  24. tlsConfig *tls.Config
  25. mirrorStatus map[string]SyncStatus
  26. }
  27. // GetTUNASyncWorker returns a singalton worker
  28. func GetTUNASyncWorker(cfg *Config) *Worker {
  29. if tunasyncWorker != nil {
  30. return tunasyncWorker
  31. }
  32. w := &Worker{
  33. cfg: cfg,
  34. providers: make(map[string]mirrorProvider),
  35. jobs: make(map[string]*mirrorJob),
  36. managerChan: make(chan jobMessage, 32),
  37. semaphore: make(chan empty, cfg.Global.Concurrent),
  38. schedule: newScheduleQueue(),
  39. mirrorStatus: make(map[string]SyncStatus),
  40. }
  41. w.initJobs()
  42. w.makeHTTPServer()
  43. tunasyncWorker = w
  44. return w
  45. }
  46. func (w *Worker) initProviders() {
  47. c := w.cfg
  48. formatLogDir := func(logDir string, m mirrorConfig) string {
  49. tmpl, err := template.New("logDirTmpl-" + m.Name).Parse(logDir)
  50. if err != nil {
  51. panic(err)
  52. }
  53. var formatedLogDir bytes.Buffer
  54. tmpl.Execute(&formatedLogDir, m)
  55. return formatedLogDir.String()
  56. }
  57. for _, mirror := range c.Mirrors {
  58. logDir := mirror.LogDir
  59. mirrorDir := mirror.MirrorDir
  60. if logDir == "" {
  61. logDir = c.Global.LogDir
  62. }
  63. if mirrorDir == "" {
  64. mirrorDir = c.Global.MirrorDir
  65. }
  66. logDir = formatLogDir(logDir, mirror)
  67. var provider mirrorProvider
  68. switch mirror.Provider {
  69. case ProvCommand:
  70. pc := cmdConfig{
  71. name: mirror.Name,
  72. upstreamURL: mirror.Upstream,
  73. command: mirror.Command,
  74. workingDir: filepath.Join(mirrorDir, mirror.Name),
  75. logDir: logDir,
  76. logFile: filepath.Join(logDir, "latest.log"),
  77. interval: time.Duration(mirror.Interval) * time.Minute,
  78. env: mirror.Env,
  79. }
  80. p, err := newCmdProvider(pc)
  81. if err != nil {
  82. panic(err)
  83. }
  84. provider = p
  85. case ProvRsync:
  86. rc := rsyncConfig{
  87. name: mirror.Name,
  88. upstreamURL: mirror.Upstream,
  89. password: mirror.Password,
  90. excludeFile: mirror.ExcludeFile,
  91. workingDir: filepath.Join(mirrorDir, mirror.Name),
  92. logDir: logDir,
  93. logFile: filepath.Join(logDir, "latest.log"),
  94. useIPv6: mirror.UseIPv6,
  95. interval: time.Duration(mirror.Interval) * time.Minute,
  96. }
  97. p, err := newRsyncProvider(rc)
  98. if err != nil {
  99. panic(err)
  100. }
  101. provider = p
  102. case ProvTwoStageRsync:
  103. rc := twoStageRsyncConfig{
  104. name: mirror.Name,
  105. stage1Profile: mirror.Stage1Profile,
  106. upstreamURL: mirror.Upstream,
  107. password: mirror.Password,
  108. excludeFile: mirror.ExcludeFile,
  109. workingDir: filepath.Join(mirrorDir, mirror.Name),
  110. logDir: logDir,
  111. logFile: filepath.Join(logDir, "latest.log"),
  112. useIPv6: mirror.UseIPv6,
  113. interval: time.Duration(mirror.Interval) * time.Minute,
  114. }
  115. p, err := newTwoStageRsyncProvider(rc)
  116. if err != nil {
  117. panic(err)
  118. }
  119. provider = p
  120. default:
  121. panic(errors.New("Invalid mirror provider"))
  122. }
  123. provider.AddHook(newLogLimiter(provider))
  124. w.providers[provider.Name()] = provider
  125. }
  126. }
  127. func (w *Worker) initJobs() {
  128. w.initProviders()
  129. for name, provider := range w.providers {
  130. w.jobs[name] = newMirrorJob(provider)
  131. go w.jobs[name].Run(w.managerChan, w.semaphore)
  132. w.mirrorStatus[name] = Paused
  133. }
  134. }
  135. // Ctrl server receives commands from the manager
  136. func (w *Worker) makeHTTPServer() {
  137. s := gin.New()
  138. s.Use(gin.Recovery())
  139. s.POST("/", func(c *gin.Context) {
  140. var cmd WorkerCmd
  141. if err := c.BindJSON(&cmd); err != nil {
  142. c.JSON(http.StatusBadRequest, gin.H{"msg": "Invalid request"})
  143. return
  144. }
  145. job, ok := w.jobs[cmd.MirrorID]
  146. if !ok {
  147. c.JSON(http.StatusNotFound, gin.H{"msg": fmt.Sprintf("Mirror ``%s'' not found", cmd.MirrorID)})
  148. return
  149. }
  150. // if job disabled, start them first
  151. switch cmd.Cmd {
  152. case CmdStart, CmdRestart:
  153. if job.Disabled() {
  154. go job.Run(w.managerChan, w.semaphore)
  155. }
  156. }
  157. switch cmd.Cmd {
  158. case CmdStart:
  159. job.ctrlChan <- jobStart
  160. case CmdStop:
  161. job.ctrlChan <- jobStop
  162. case CmdRestart:
  163. job.ctrlChan <- jobRestart
  164. case CmdDisable:
  165. w.schedule.Remove(job.Name())
  166. job.ctrlChan <- jobDisable
  167. <-job.disabled
  168. case CmdPing:
  169. job.ctrlChan <- jobStart
  170. default:
  171. c.JSON(http.StatusNotAcceptable, gin.H{"msg": "Invalid Command"})
  172. return
  173. }
  174. c.JSON(http.StatusOK, gin.H{"msg": "OK"})
  175. })
  176. w.httpServer = s
  177. }
  178. func (w *Worker) runHTTPServer() {
  179. addr := fmt.Sprintf("%s:%d", w.cfg.Server.Addr, w.cfg.Server.Port)
  180. if w.cfg.Server.SSLCert == "" && w.cfg.Server.SSLKey == "" {
  181. if err := w.httpServer.Run(addr); err != nil {
  182. panic(err)
  183. }
  184. } else {
  185. if err := w.httpServer.RunTLS(addr, w.cfg.Server.SSLCert, w.cfg.Server.SSLKey); err != nil {
  186. panic(err)
  187. }
  188. }
  189. }
  190. // Run runs worker forever
  191. func (w *Worker) Run() {
  192. w.registorWorker()
  193. go w.runHTTPServer()
  194. w.runSchedule()
  195. }
  196. func (w *Worker) runSchedule() {
  197. mirrorList := w.fetchJobStatus()
  198. unset := make(map[string]bool)
  199. for name := range w.jobs {
  200. unset[name] = true
  201. }
  202. for _, m := range mirrorList {
  203. if job, ok := w.jobs[m.Name]; ok {
  204. stime := m.LastUpdate.Add(job.provider.Interval())
  205. w.schedule.AddJob(stime, job)
  206. delete(unset, m.Name)
  207. }
  208. }
  209. for name := range unset {
  210. job := w.jobs[name]
  211. w.schedule.AddJob(time.Now(), job)
  212. }
  213. for {
  214. select {
  215. case jobMsg := <-w.managerChan:
  216. // got status update from job
  217. w.updateStatus(jobMsg)
  218. status := w.mirrorStatus[jobMsg.name]
  219. if status == Disabled || status == Paused {
  220. continue
  221. }
  222. w.mirrorStatus[jobMsg.name] = jobMsg.status
  223. switch jobMsg.status {
  224. case Success, Failed:
  225. job := w.jobs[jobMsg.name]
  226. w.schedule.AddJob(
  227. time.Now().Add(job.provider.Interval()),
  228. job,
  229. )
  230. }
  231. case <-time.Tick(10 * time.Second):
  232. if job := w.schedule.Pop(); job != nil {
  233. job.ctrlChan <- jobStart
  234. }
  235. }
  236. }
  237. }
  238. // Name returns worker name
  239. func (w *Worker) Name() string {
  240. return w.cfg.Global.Name
  241. }
  242. // URL returns the url to http server of the worker
  243. func (w *Worker) URL() string {
  244. proto := "https"
  245. if w.cfg.Server.SSLCert == "" && w.cfg.Server.SSLKey == "" {
  246. proto = "http"
  247. }
  248. return fmt.Sprintf("%s://%s:%d/", proto, w.cfg.Server.Hostname, w.cfg.Server.Port)
  249. }
  250. func (w *Worker) registorWorker() {
  251. url := fmt.Sprintf(
  252. "%s/workers",
  253. w.cfg.Manager.APIBase,
  254. )
  255. msg := WorkerStatus{
  256. ID: w.Name(),
  257. URL: w.URL(),
  258. }
  259. if _, err := PostJSON(url, msg, w.tlsConfig); err != nil {
  260. logger.Error("Failed to register worker")
  261. }
  262. }
  263. func (w *Worker) updateStatus(jobMsg jobMessage) {
  264. url := fmt.Sprintf(
  265. "%s/%s/jobs/%s",
  266. w.cfg.Manager.APIBase,
  267. w.Name(),
  268. jobMsg.name,
  269. )
  270. p := w.providers[jobMsg.name]
  271. smsg := MirrorStatus{
  272. Name: jobMsg.name,
  273. Worker: w.cfg.Global.Name,
  274. IsMaster: true,
  275. Status: jobMsg.status,
  276. LastUpdate: time.Now(),
  277. Upstream: p.Upstream(),
  278. Size: "unknown",
  279. ErrorMsg: jobMsg.msg,
  280. }
  281. if _, err := PostJSON(url, smsg, w.tlsConfig); err != nil {
  282. logger.Error("Failed to update mirror(%s) status: %s", jobMsg.name, err.Error())
  283. }
  284. }
  285. func (w *Worker) fetchJobStatus() []MirrorStatus {
  286. var mirrorList []MirrorStatus
  287. url := fmt.Sprintf(
  288. "%s/%s/jobs",
  289. w.cfg.Manager.APIBase,
  290. w.Name(),
  291. )
  292. if _, err := GetJSON(url, &mirrorList, w.tlsConfig); err != nil {
  293. logger.Error("Failed to fetch job status: %s", err.Error())
  294. }
  295. return mirrorList
  296. }