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  }