github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/admin.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"os"
    13  	"runtime/debug"
    14  	"time"
    15  
    16  	"github.com/mattermost/mattermost-server/v5/mlog"
    17  	"github.com/mattermost/mattermost-server/v5/model"
    18  	"github.com/mattermost/mattermost-server/v5/services/mailservice"
    19  	"github.com/mattermost/mattermost-server/v5/utils"
    20  )
    21  
    22  func (s *Server) GetLogs(page, perPage int) ([]string, *model.AppError) {
    23  	var lines []string
    24  
    25  	license := s.License()
    26  	if license != nil && *license.Features.Cluster && s.Cluster != nil && *s.Config().ClusterSettings.Enable {
    27  		if info := s.Cluster.GetMyClusterInfo(); info != nil {
    28  			lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
    29  			lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
    30  			lines = append(lines, info.Hostname)
    31  			lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
    32  			lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
    33  		} else {
    34  			mlog.Error("Could not get cluster info")
    35  		}
    36  	}
    37  
    38  	melines, err := s.GetLogsSkipSend(page, perPage)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	lines = append(lines, melines...)
    44  
    45  	if s.Cluster != nil && *s.Config().ClusterSettings.Enable {
    46  		clines, err := s.Cluster.GetLogs(page, perPage)
    47  		if err != nil {
    48  			return nil, err
    49  		}
    50  
    51  		lines = append(lines, clines...)
    52  	}
    53  
    54  	return lines, nil
    55  }
    56  
    57  func (a *App) GetLogs(page, perPage int) ([]string, *model.AppError) {
    58  	return a.Srv().GetLogs(page, perPage)
    59  }
    60  
    61  func (s *Server) GetLogsSkipSend(page, perPage int) ([]string, *model.AppError) {
    62  	var lines []string
    63  
    64  	if *s.Config().LogSettings.EnableFile {
    65  		timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), mlog.DefaultFlushTimeout)
    66  		defer timeoutCancel()
    67  		mlog.Flush(timeoutCtx)
    68  
    69  		logFile := utils.GetLogFileLocation(*s.Config().LogSettings.FileLocation)
    70  		file, err := os.Open(logFile)
    71  		if err != nil {
    72  			return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
    73  		}
    74  
    75  		defer file.Close()
    76  
    77  		var newLine = []byte{'\n'}
    78  		var lineCount int
    79  		const searchPos = -1
    80  		b := make([]byte, 1)
    81  		var endOffset int64 = 0
    82  
    83  		// if the file exists and it's last byte is '\n' - skip it
    84  		var stat os.FileInfo
    85  		if stat, err = os.Stat(logFile); err == nil {
    86  			if _, err = file.ReadAt(b, stat.Size()-1); err == nil && b[0] == newLine[0] {
    87  				endOffset = -1
    88  			}
    89  		}
    90  		lineEndPos, err := file.Seek(endOffset, io.SeekEnd)
    91  		if err != nil {
    92  			return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
    93  		}
    94  		for {
    95  			pos, err := file.Seek(searchPos, io.SeekCurrent)
    96  			if err != nil {
    97  				return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
    98  			}
    99  
   100  			_, err = file.ReadAt(b, pos)
   101  			if err != nil {
   102  				return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
   103  			}
   104  
   105  			if b[0] == newLine[0] || pos == 0 {
   106  				lineCount++
   107  				if lineCount > page*perPage {
   108  					line := make([]byte, lineEndPos-pos)
   109  					_, err := file.ReadAt(line, pos)
   110  					if err != nil {
   111  						return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
   112  					}
   113  					lines = append(lines, string(line))
   114  				}
   115  				if pos == 0 {
   116  					break
   117  				}
   118  				lineEndPos = pos
   119  			}
   120  
   121  			if len(lines) == perPage {
   122  				break
   123  			}
   124  		}
   125  
   126  		for i, j := 0, len(lines)-1; i < j; i, j = i+1, j-1 {
   127  			lines[i], lines[j] = lines[j], lines[i]
   128  		}
   129  	} else {
   130  		lines = append(lines, "")
   131  	}
   132  
   133  	return lines, nil
   134  }
   135  
   136  func (a *App) GetLogsSkipSend(page, perPage int) ([]string, *model.AppError) {
   137  	return a.Srv().GetLogsSkipSend(page, perPage)
   138  }
   139  
   140  func (a *App) GetClusterStatus() []*model.ClusterInfo {
   141  	infos := make([]*model.ClusterInfo, 0)
   142  
   143  	if a.Cluster() != nil {
   144  		infos = a.Cluster().GetClusterInfos()
   145  	}
   146  
   147  	return infos
   148  }
   149  
   150  func (s *Server) InvalidateAllCaches() *model.AppError {
   151  	debug.FreeOSMemory()
   152  	s.InvalidateAllCachesSkipSend()
   153  
   154  	if s.Cluster != nil {
   155  
   156  		msg := &model.ClusterMessage{
   157  			Event:            model.CLUSTER_EVENT_INVALIDATE_ALL_CACHES,
   158  			SendType:         model.CLUSTER_SEND_RELIABLE,
   159  			WaitForAllToSend: true,
   160  		}
   161  
   162  		s.Cluster.SendClusterMessage(msg)
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  func (s *Server) InvalidateAllCachesSkipSend() {
   169  	mlog.Info("Purging all caches")
   170  	s.sessionCache.Purge()
   171  	s.statusCache.Purge()
   172  	s.Store.Team().ClearCaches()
   173  	s.Store.Channel().ClearCaches()
   174  	s.Store.User().ClearCaches()
   175  	s.Store.Post().ClearCaches()
   176  	s.Store.FileInfo().ClearCaches()
   177  	s.Store.Webhook().ClearCaches()
   178  	s.LoadLicense()
   179  }
   180  
   181  func (a *App) RecycleDatabaseConnection() {
   182  	mlog.Info("Attempting to recycle database connections.")
   183  
   184  	// This works by setting 10 seconds as the max conn lifetime for all DB connections.
   185  	// This allows in gradually closing connections as they expire. In future, we can think
   186  	// of exposing this as a param from the REST api.
   187  	a.Srv().Store.RecycleDBConnections(10 * time.Second)
   188  
   189  	mlog.Info("Finished recycling database connections.")
   190  }
   191  
   192  func (a *App) TestSiteURL(siteURL string) *model.AppError {
   193  	url := fmt.Sprintf("%s/api/v4/system/ping", siteURL)
   194  	res, err := http.Get(url)
   195  	if err != nil || res.StatusCode != 200 {
   196  		return model.NewAppError("testSiteURL", "app.admin.test_site_url.failure", nil, "", http.StatusBadRequest)
   197  	}
   198  	defer func() {
   199  		_, _ = io.Copy(ioutil.Discard, res.Body)
   200  		_ = res.Body.Close()
   201  	}()
   202  
   203  	return nil
   204  }
   205  
   206  func (a *App) TestEmail(userID string, cfg *model.Config) *model.AppError {
   207  	if *cfg.EmailSettings.SMTPServer == "" {
   208  		return model.NewAppError("testEmail", "api.admin.test_email.missing_server", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}), http.StatusBadRequest)
   209  	}
   210  
   211  	// if the user hasn't changed their email settings, fill in the actual SMTP password so that
   212  	// the user can verify an existing SMTP connection
   213  	if *cfg.EmailSettings.SMTPPassword == model.FAKE_SETTING {
   214  		if *cfg.EmailSettings.SMTPServer == *a.Config().EmailSettings.SMTPServer &&
   215  			*cfg.EmailSettings.SMTPPort == *a.Config().EmailSettings.SMTPPort &&
   216  			*cfg.EmailSettings.SMTPUsername == *a.Config().EmailSettings.SMTPUsername {
   217  			*cfg.EmailSettings.SMTPPassword = *a.Config().EmailSettings.SMTPPassword
   218  		} else {
   219  			return model.NewAppError("testEmail", "api.admin.test_email.reenter_password", nil, "", http.StatusBadRequest)
   220  		}
   221  	}
   222  	user, err := a.GetUser(userID)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	T := utils.GetUserTranslations(user.Locale)
   228  	license := a.Srv().License()
   229  	mailConfig := a.Srv().MailServiceConfig()
   230  	if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), mailConfig, license != nil && *license.Features.Compliance, ""); err != nil {
   231  		return model.NewAppError("testEmail", "app.admin.test_email.failure", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
   232  	}
   233  
   234  	return nil
   235  }
   236  
   237  // ServerBusyStateChanged is called when a CLUSTER_EVENT_BUSY_STATE_CHANGED is received.
   238  func (a *App) ServerBusyStateChanged(sbs *model.ServerBusyState) {
   239  	a.Srv().Busy.ClusterEventChanged(sbs)
   240  	if sbs.Busy {
   241  		mlog.Warn("server busy state activitated via cluster event - non-critical services disabled", mlog.Int64("expires_sec", sbs.Expires))
   242  	} else {
   243  		mlog.Info("server busy state cleared via cluster event - non-critical services enabled")
   244  	}
   245  }