github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/jobs/import_process/worker.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package import_process 5 6 import ( 7 "archive/zip" 8 "io" 9 "net/http" 10 "path/filepath" 11 "runtime" 12 "strconv" 13 "strings" 14 15 "github.com/masterhung0112/hk_server/v5/app" 16 "github.com/masterhung0112/hk_server/v5/app/request" 17 "github.com/masterhung0112/hk_server/v5/jobs" 18 tjobs "github.com/masterhung0112/hk_server/v5/jobs/interfaces" 19 "github.com/masterhung0112/hk_server/v5/model" 20 "github.com/masterhung0112/hk_server/v5/shared/mlog" 21 ) 22 23 func init() { 24 app.RegisterJobsImportProcessInterface(func(s *app.Server) tjobs.ImportProcessInterface { 25 a := app.New(app.ServerConnector(s)) 26 return &ImportProcessInterfaceImpl{a} 27 }) 28 } 29 30 type ImportProcessInterfaceImpl struct { 31 app *app.App 32 } 33 34 type ImportProcessWorker struct { 35 name string 36 stopChan chan struct{} 37 stoppedChan chan struct{} 38 jobsChan chan model.Job 39 jobServer *jobs.JobServer 40 app *app.App 41 appContext *request.Context 42 } 43 44 func (i *ImportProcessInterfaceImpl) MakeWorker() model.Worker { 45 return &ImportProcessWorker{ 46 name: "ImportProcess", 47 stopChan: make(chan struct{}), 48 stoppedChan: make(chan struct{}), 49 jobsChan: make(chan model.Job), 50 jobServer: i.app.Srv().Jobs, 51 app: i.app, 52 appContext: &request.Context{}, 53 } 54 } 55 56 func (w *ImportProcessWorker) JobChannel() chan<- model.Job { 57 return w.jobsChan 58 } 59 60 func (w *ImportProcessWorker) Run() { 61 mlog.Debug("Worker started", mlog.String("worker", w.name)) 62 63 defer func() { 64 mlog.Debug("Worker finished", mlog.String("worker", w.name)) 65 close(w.stoppedChan) 66 }() 67 68 for { 69 select { 70 case <-w.stopChan: 71 mlog.Debug("Worker received stop signal", mlog.String("worker", w.name)) 72 return 73 case job := <-w.jobsChan: 74 mlog.Debug("Worker received a new candidate job.", mlog.String("worker", w.name)) 75 w.doJob(&job) 76 } 77 } 78 } 79 80 func (w *ImportProcessWorker) Stop() { 81 mlog.Debug("Worker stopping", mlog.String("worker", w.name)) 82 close(w.stopChan) 83 <-w.stoppedChan 84 } 85 86 func (w *ImportProcessWorker) doJob(job *model.Job) { 87 if claimed, err := w.jobServer.ClaimJob(job); err != nil { 88 mlog.Warn("Worker experienced an error while trying to claim job", 89 mlog.String("worker", w.name), 90 mlog.String("job_id", job.Id), 91 mlog.String("error", err.Error())) 92 return 93 } else if !claimed { 94 return 95 } 96 97 importFileName, ok := job.Data["import_file"] 98 if !ok { 99 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.missing_file", nil, "", http.StatusBadRequest) 100 w.setJobError(job, appError) 101 return 102 } 103 104 importFilePath := filepath.Join(*w.app.Config().ImportSettings.Directory, importFileName) 105 if ok, err := w.app.FileExists(importFilePath); err != nil { 106 w.setJobError(job, err) 107 return 108 } else if !ok { 109 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.file_exists", nil, "", http.StatusBadRequest) 110 w.setJobError(job, appError) 111 return 112 } 113 114 importFileSize, appErr := w.app.FileSize(importFilePath) 115 if appErr != nil { 116 w.setJobError(job, appErr) 117 return 118 } 119 120 importFile, appErr := w.app.FileReader(importFilePath) 121 if appErr != nil { 122 w.setJobError(job, appErr) 123 return 124 } 125 defer importFile.Close() 126 127 importZipReader, err := zip.NewReader(importFile.(io.ReaderAt), importFileSize) 128 if err != nil { 129 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, err.Error(), http.StatusInternalServerError) 130 w.setJobError(job, appError) 131 return 132 } 133 134 // find JSONL import file. 135 var jsonFile io.ReadCloser 136 for _, f := range importZipReader.File { 137 if filepath.Ext(f.Name) != ".jsonl" { 138 continue 139 } 140 // avoid "zip slip" 141 if strings.Contains(f.Name, "..") { 142 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, "jsonFilePath contains path traversal", http.StatusForbidden) 143 w.setJobError(job, appError) 144 return 145 } 146 147 jsonFile, err = f.Open() 148 if err != nil { 149 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, err.Error(), http.StatusInternalServerError) 150 w.setJobError(job, appError) 151 return 152 } 153 154 defer jsonFile.Close() 155 break 156 } 157 158 if jsonFile == nil { 159 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.missing_jsonl", nil, "jsonFile was nil", http.StatusBadRequest) 160 w.setJobError(job, appError) 161 return 162 } 163 164 // do the actual import. 165 appErr, lineNumber := w.app.BulkImport(w.appContext, jsonFile, importZipReader, false, runtime.NumCPU()) 166 if appErr != nil { 167 job.Data["line_number"] = strconv.Itoa(lineNumber) 168 w.setJobError(job, appErr) 169 return 170 } 171 172 // remove import file when done. 173 if appErr := w.app.RemoveFile(importFilePath); appErr != nil { 174 w.setJobError(job, appErr) 175 return 176 } 177 178 mlog.Info("Worker: Job is complete", mlog.String("worker", w.name), mlog.String("job_id", job.Id)) 179 w.setJobSuccess(job) 180 } 181 182 func (w *ImportProcessWorker) setJobSuccess(job *model.Job) { 183 if err := w.app.Srv().Jobs.SetJobSuccess(job); err != nil { 184 mlog.Error("Worker: Failed to set success for job", mlog.String("worker", w.name), mlog.String("job_id", job.Id), mlog.String("error", err.Error())) 185 w.setJobError(job, err) 186 } 187 } 188 189 func (w *ImportProcessWorker) setJobError(job *model.Job, appError *model.AppError) { 190 if err := w.app.Srv().Jobs.SetJobError(job, appError); err != nil { 191 mlog.Error("Worker: Failed to set job error", mlog.String("worker", w.name), mlog.String("job_id", job.Id), mlog.String("error", err.Error())) 192 } 193 }