Commit b112747d authored by Pierre de La Morinerie's avatar Pierre de La Morinerie Committed by Christopher Speller

Send systemd READY notification (#8296)

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.
parent 2930766c
......@@ -4,6 +4,7 @@
package main
import (
"net"
"os"
"os/signal"
"syscall"
......@@ -174,6 +175,8 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan
a.Jobs.StartSchedulers()
}
notifyReady()
// wait for kill signal before attempting to gracefully shutdown
// the running service
signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
......@@ -244,6 +247,35 @@ func doDiagnostics(a *app.App) {
}
}
func notifyReady() {
// If the environment vars provide a systemd notification socket,
// notify systemd that the server is ready.
systemdSocket := os.Getenv("NOTIFY_SOCKET")
if systemdSocket != "" {
l4g.Info("Sending systemd READY notification.")
err := sendSystemdReadyNotification(systemdSocket)
if err != nil {
l4g.Error(err.Error())
}
}
}
func sendSystemdReadyNotification(socketPath string) error {
msg := "READY=1"
addr := &net.UnixAddr{
Name: socketPath,
Net: "unixgram",
}
conn, err := net.DialUnix(addr.Net, nil, addr)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write([]byte(msg))
return err
}
func doTokenCleanup(a *app.App) {
a.Srv.Store.Token().Cleanup()
}
......
......@@ -5,6 +5,7 @@ package main
import (
"io/ioutil"
"net"
"os"
"syscall"
"testing"
......@@ -70,3 +71,66 @@ func TestRunServerInvalidConfigFile(t *testing.T) {
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)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment