github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/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 "io" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "path/filepath" 12 "runtime" 13 "strconv" 14 15 "github.com/mattermost/mattermost-server/v5/app" 16 "github.com/mattermost/mattermost-server/v5/jobs" 17 tjobs "github.com/mattermost/mattermost-server/v5/jobs/interfaces" 18 "github.com/mattermost/mattermost-server/v5/mlog" 19 "github.com/mattermost/mattermost-server/v5/model" 20 "github.com/mattermost/mattermost-server/v5/utils" 21 ) 22 23 func init() { 24 app.RegisterJobsImportProcessInterface(func(a *app.App) tjobs.ImportProcessInterface { 25 return &ImportProcessInterfaceImpl{a} 26 }) 27 } 28 29 type ImportProcessInterfaceImpl struct { 30 app *app.App 31 } 32 33 type ImportProcessWorker struct { 34 name string 35 stopChan chan struct{} 36 stoppedChan chan struct{} 37 jobsChan chan model.Job 38 jobServer *jobs.JobServer 39 app *app.App 40 } 41 42 func (i *ImportProcessInterfaceImpl) MakeWorker() model.Worker { 43 return &ImportProcessWorker{ 44 name: "ImportProcess", 45 stopChan: make(chan struct{}), 46 stoppedChan: make(chan struct{}), 47 jobsChan: make(chan model.Job), 48 jobServer: i.app.Srv().Jobs, 49 app: i.app, 50 } 51 } 52 53 func (w *ImportProcessWorker) JobChannel() chan<- model.Job { 54 return w.jobsChan 55 } 56 57 func (w *ImportProcessWorker) Run() { 58 mlog.Debug("Worker started", mlog.String("worker", w.name)) 59 60 defer func() { 61 mlog.Debug("Worker finished", mlog.String("worker", w.name)) 62 close(w.stoppedChan) 63 }() 64 65 for { 66 select { 67 case <-w.stopChan: 68 mlog.Debug("Worker received stop signal", mlog.String("worker", w.name)) 69 return 70 case job := <-w.jobsChan: 71 mlog.Debug("Worker received a new candidate job.", mlog.String("worker", w.name)) 72 w.doJob(&job) 73 } 74 } 75 } 76 77 func (w *ImportProcessWorker) Stop() { 78 mlog.Debug("Worker stopping", mlog.String("worker", w.name)) 79 close(w.stopChan) 80 <-w.stoppedChan 81 } 82 83 func (w *ImportProcessWorker) doJob(job *model.Job) { 84 if claimed, err := w.jobServer.ClaimJob(job); err != nil { 85 mlog.Warn("Worker experienced an error while trying to claim job", 86 mlog.String("worker", w.name), 87 mlog.String("job_id", job.Id), 88 mlog.String("error", err.Error())) 89 return 90 } else if !claimed { 91 return 92 } 93 94 importFileName, ok := job.Data["import_file"] 95 if !ok { 96 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.missing_file", nil, "", http.StatusBadRequest) 97 w.setJobError(job, appError) 98 return 99 } 100 101 importFilePath := filepath.Join(*w.app.Config().ImportSettings.Directory, importFileName) 102 if ok, err := w.app.FileExists(importFilePath); err != nil { 103 w.setJobError(job, err) 104 return 105 } else if !ok { 106 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.file_exists", nil, "", http.StatusBadRequest) 107 w.setJobError(job, appError) 108 return 109 } 110 111 importFileSize, appErr := w.app.FileSize(importFilePath) 112 if appErr != nil { 113 w.setJobError(job, appErr) 114 return 115 } 116 117 importFile, appErr := w.app.FileReader(importFilePath) 118 if appErr != nil { 119 w.setJobError(job, appErr) 120 return 121 } 122 defer importFile.Close() 123 124 // TODO (MM-30187): improve this process by eliminating the need to unzip the import 125 // file locally and instead do the whole bulk import process in memory by 126 // streaming the import file. 127 128 // create a temporary dir to extract the zipped import file. 129 dir, err := ioutil.TempDir("", "import") 130 if err != nil { 131 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.tmp_dir", nil, err.Error(), http.StatusInternalServerError) 132 w.setJobError(job, appError) 133 return 134 } 135 defer os.RemoveAll(dir) 136 137 // extract the contents of the zipped file. 138 paths, err := utils.UnzipToPath(importFile.(io.ReaderAt), importFileSize, dir) 139 if err != nil { 140 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.unzip", nil, err.Error(), http.StatusInternalServerError) 141 w.setJobError(job, appError) 142 return 143 } 144 145 // find JSONL import file. 146 var jsonFilePath string 147 for _, path := range paths { 148 if filepath.Ext(path) == ".jsonl" { 149 jsonFilePath = path 150 break 151 } 152 } 153 154 if jsonFilePath == "" { 155 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.missing_jsonl", nil, "", http.StatusBadRequest) 156 w.setJobError(job, appError) 157 return 158 } 159 160 jsonFile, err := os.Open(jsonFilePath) 161 if err != nil { 162 appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, err.Error(), http.StatusInternalServerError) 163 w.setJobError(job, appError) 164 return 165 } 166 167 // do the actual import. 168 appErr, lineNumber := w.app.BulkImportWithPath(jsonFile, false, runtime.NumCPU(), filepath.Join(dir, app.ExportDataDir)) 169 if appErr != nil { 170 job.Data["line_number"] = strconv.Itoa(lineNumber) 171 w.setJobError(job, appErr) 172 return 173 } 174 175 // remove import file when done. 176 if appErr := w.app.RemoveFile(importFilePath); appErr != nil { 177 w.setJobError(job, appErr) 178 return 179 } 180 181 mlog.Info("Worker: Job is complete", mlog.String("worker", w.name), mlog.String("job_id", job.Id)) 182 w.setJobSuccess(job) 183 } 184 185 func (w *ImportProcessWorker) setJobSuccess(job *model.Job) { 186 if err := w.app.Srv().Jobs.SetJobSuccess(job); err != nil { 187 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())) 188 w.setJobError(job, err) 189 } 190 } 191 192 func (w *ImportProcessWorker) setJobError(job *model.Job, appError *model.AppError) { 193 if err := w.app.Srv().Jobs.SetJobError(job, appError); err != nil { 194 mlog.Error("Worker: Failed to set job error", mlog.String("worker", w.name), mlog.String("job_id", job.Id), mlog.String("error", err.Error())) 195 } 196 }