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  }