mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Currently, when starting Mattermost programmatically, it's hard to tell when the server is actually ready to receive network connections. This isn't convenient for monitoring (the systemd service status is "running" although the server is still booting), nor for programatic use (where a script would need to know when the server is ready to perform further actions). To improve this, systemd allow processes to tell when they started successfully. The launcher waits for this notification before reporting the service as successfully launched. The way processes notify systemd is by sending a `READY=1` string over a standard unix socket, whose path is provided in an environment var. The systemd service is then told to expect this notification: ```diff [Service] -Type=simple +Type=notify ExecStart=/home/vagrant/go/bin/platform ``` Now, when starting the server, systemd will actually wait for the server to be ready before returning the control to the shell. Additionally, during this time, querying the server status with `service mattermost status` will report the service as "activating" – before transitioning to "running" when the server is ready.
137 lines
3.7 KiB
Go
137 lines
3.7 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"syscall"
|
|
"testing"
|
|
|
|
"github.com/mattermost/mattermost-server/jobs"
|
|
"github.com/mattermost/mattermost-server/utils"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type ServerTestHelper struct {
|
|
configPath string
|
|
disableConfigWatch bool
|
|
interruptChan chan os.Signal
|
|
originalInterval int
|
|
}
|
|
|
|
func SetupServerTest() *ServerTestHelper {
|
|
// Build a channel that will be used by the server to receive system signals…
|
|
interruptChan := make(chan os.Signal, 1)
|
|
// …and sent it immediately a SIGINT value.
|
|
// This will make the server loop stop as soon as it started successfully.
|
|
interruptChan <- syscall.SIGINT
|
|
|
|
// Let jobs poll for termination every 0.2s (instead of every 15s by default)
|
|
// Otherwise we would have to wait the whole polling duration before the test
|
|
// terminates.
|
|
originalInterval := jobs.DEFAULT_WATCHER_POLLING_INTERVAL
|
|
jobs.DEFAULT_WATCHER_POLLING_INTERVAL = 200
|
|
|
|
th := &ServerTestHelper{
|
|
configPath: utils.FindConfigFile("config.json"),
|
|
disableConfigWatch: true,
|
|
interruptChan: interruptChan,
|
|
originalInterval: originalInterval,
|
|
}
|
|
return th
|
|
}
|
|
|
|
func (th *ServerTestHelper) TearDownServerTest() {
|
|
jobs.DEFAULT_WATCHER_POLLING_INTERVAL = th.originalInterval
|
|
}
|
|
|
|
func TestRunServerSuccess(t *testing.T) {
|
|
th := SetupServerTest()
|
|
defer th.TearDownServerTest()
|
|
|
|
err := runServer(th.configPath, th.disableConfigWatch, th.interruptChan)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestRunServerInvalidConfigFile(t *testing.T) {
|
|
th := SetupServerTest()
|
|
defer th.TearDownServerTest()
|
|
|
|
// Start the server with an unreadable config file
|
|
unreadableConfigFile, err := ioutil.TempFile("", "mattermost-unreadable-config-file-")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
os.Chmod(unreadableConfigFile.Name(), 0200)
|
|
defer os.Remove(unreadableConfigFile.Name())
|
|
|
|
err = runServer(unreadableConfigFile.Name(), th.disableConfigWatch, th.interruptChan)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestRunServerSystemdNotification(t *testing.T) {
|
|
th := SetupServerTest()
|
|
defer th.TearDownServerTest()
|
|
|
|
// Get a random temporary filename for using as a mock systemd socket
|
|
socketFile, err := ioutil.TempFile("", "mattermost-systemd-mock-socket-")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
socketPath := socketFile.Name()
|
|
os.Remove(socketPath)
|
|
|
|
// Set the socket path in the process environment
|
|
originalSocket := os.Getenv("NOTIFY_SOCKET")
|
|
os.Setenv("NOTIFY_SOCKET", socketPath)
|
|
defer os.Setenv("NOTIFY_SOCKET", originalSocket)
|
|
|
|
// Open the socket connection
|
|
addr := &net.UnixAddr{
|
|
Name: socketPath,
|
|
Net: "unixgram",
|
|
}
|
|
connection, err := net.ListenUnixgram("unixgram", addr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer connection.Close()
|
|
defer os.Remove(socketPath)
|
|
|
|
// Listen for socket data
|
|
socketReader := make(chan string)
|
|
go func(ch chan string) {
|
|
buffer := make([]byte, 512)
|
|
count, err := connection.Read(buffer)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
data := buffer[0:count]
|
|
ch<- string(data)
|
|
}(socketReader)
|
|
|
|
// Start and stop the server
|
|
err = runServer(th.configPath, th.disableConfigWatch, th.interruptChan)
|
|
require.NoError(t, err)
|
|
|
|
// Ensure the notification has been sent on the socket and is correct
|
|
notification := <-socketReader
|
|
require.Equal(t, notification, "READY=1")
|
|
}
|
|
|
|
func TestRunServerNoSystemd(t *testing.T) {
|
|
th := SetupServerTest()
|
|
defer th.TearDownServerTest()
|
|
|
|
// Temporarily remove any Systemd socket defined in the environment
|
|
originalSocket := os.Getenv("NOTIFY_SOCKET")
|
|
os.Unsetenv("NOTIFY_SOCKET")
|
|
defer os.Setenv("NOTIFY_SOCKET", originalSocket)
|
|
|
|
err := runServer(th.configPath, th.disableConfigWatch, th.interruptChan)
|
|
require.NoError(t, err)
|
|
}
|