mirror of
https://github.com/containers/podman.git
synced 2026-11-08 17:12:52 -05:00
systemd attributes a notification to a unit by the cgroup of its sender when it reads the message. The podman process that sends READY for --sdnotify=healthy exits right after sending it, and when systemd reads the message only then, it is dropped and the unit runs into its start timeout. Add SendMessageWithBarrier, which sends BARRIER=1 with a pipe fd after the message and waits up to five seconds until the receiver closes it, as sd_notify_barrier(3) does. Use it where podman may exit after notifying: MAINPID (and READY) after starting a container, READY once it is healthy, and the fast path of kube play. A failed barrier is only logged at debug level since the message itself was sent. Also parse only the received control message in the proxy's listener. It parsed the whole buffer, failed, and never closed the barrier fds. The sdnotify system tests now receive on the notify socket with a new podman-testing sd-notify-listen command instead of socat. socat keeps the fds it receives, so every barrier ran into its timeout; the new command closes them right away. BARRIER=1 is not logged. Fixes: #29998 Signed-off-by: Tobias Wiesing <tobias@hueske.digital>
138 lines
3.6 KiB
Go
138 lines
3.6 KiB
Go
//go:build !windows
|
|
|
|
package notifyproxy
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/coreos/go-systemd/v22/daemon"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Helper function to send the specified message over the socket of the proxy.
|
|
func sendMessage(t *testing.T, proxy *NotifyProxy, message string) {
|
|
err := SendMessage(proxy.SocketPath(), message)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSendMessageWaitsForBarrier(t *testing.T) {
|
|
socketPath := filepath.Join(t.TempDir(), "notify.sock")
|
|
conn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: socketPath, Net: "unixgram"})
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
|
|
sent := make(chan error, 1)
|
|
go func() {
|
|
sent <- SendMessageWithBarrier(socketPath, daemon.SdNotifyReady)
|
|
}()
|
|
|
|
require.NoError(t, conn.SetReadDeadline(time.Now().Add(_notifyBarrierTimeout)))
|
|
buf := make([]byte, _notifyBufferMax)
|
|
oob := make([]byte, unix.CmsgSpace(4))
|
|
n, err := conn.Read(buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, daemon.SdNotifyReady, string(buf[:n]))
|
|
|
|
n, oobn, _, _, err := conn.ReadMsgUnix(buf, oob)
|
|
require.NoError(t, err)
|
|
scms, err := unix.ParseSocketControlMessage(oob[:oobn])
|
|
require.NoError(t, err)
|
|
require.Len(t, scms, 1)
|
|
fds, err := unix.ParseUnixRights(&scms[0])
|
|
require.NoError(t, err)
|
|
barriers := make([]*os.File, len(fds))
|
|
for i, fd := range fds {
|
|
barriers[i] = os.NewFile(uintptr(fd), "barrier")
|
|
defer barriers[i].Close()
|
|
}
|
|
require.Len(t, barriers, 1)
|
|
require.Equal(t, _notifyBarrierMsg, string(buf[:n]))
|
|
|
|
select {
|
|
case err := <-sent:
|
|
t.Fatalf("SendMessage returned before the barrier was closed: %v", err)
|
|
default:
|
|
}
|
|
|
|
require.NoError(t, barriers[0].Close())
|
|
select {
|
|
case err := <-sent:
|
|
require.NoError(t, err)
|
|
case <-time.After(_notifyBarrierTimeout):
|
|
t.Fatal("SendMessage did not return after the barrier was closed")
|
|
}
|
|
}
|
|
|
|
func TestSendMessageWithoutBarrierSupport(t *testing.T) {
|
|
socketPath := filepath.Join(t.TempDir(), "notify.sock")
|
|
conn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: socketPath, Net: "unixgram"})
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
|
|
// Nobody reads the socket, so the barrier fd is never closed.
|
|
start := time.Now()
|
|
require.NoError(t, SendMessageWithBarrier(socketPath, daemon.SdNotifyReady))
|
|
require.GreaterOrEqual(t, time.Since(start), _notifyBarrierTimeout)
|
|
}
|
|
|
|
func TestProxyClosesBarrierFd(t *testing.T) {
|
|
proxy, err := New("")
|
|
require.NoError(t, err)
|
|
defer proxy.Close()
|
|
|
|
start := time.Now()
|
|
require.NoError(t, SendMessageWithBarrier(proxy.SocketPath(), "foo"))
|
|
require.Less(t, time.Since(start), _notifyBarrierTimeout)
|
|
}
|
|
|
|
func TestNotifyProxy(t *testing.T) {
|
|
proxy, err := New("")
|
|
require.NoError(t, err)
|
|
require.FileExists(t, proxy.SocketPath())
|
|
require.NoError(t, proxy.Close())
|
|
require.NoFileExists(t, proxy.SocketPath())
|
|
}
|
|
|
|
func TestWaitAndClose(t *testing.T) {
|
|
proxy, err := New("")
|
|
require.NoError(t, err)
|
|
require.FileExists(t, proxy.SocketPath())
|
|
|
|
ch := make(chan error)
|
|
defer func() {
|
|
err := proxy.Close()
|
|
require.NoError(t, err, "proxy should close successfully")
|
|
}()
|
|
go func() {
|
|
ch <- proxy.Wait()
|
|
}()
|
|
|
|
sendMessage(t, proxy, "foo\n")
|
|
time.Sleep(250 * time.Millisecond)
|
|
select {
|
|
case err := <-ch:
|
|
t.Fatalf("Should still be waiting but received %v", err)
|
|
default:
|
|
}
|
|
|
|
sendMessage(t, proxy, daemon.SdNotifyReady+"\nsomething else\n")
|
|
done := func() bool {
|
|
for i := range 10 {
|
|
select {
|
|
case err := <-ch:
|
|
require.NoError(t, err, "Waiting should succeed")
|
|
return true
|
|
default:
|
|
time.Sleep(time.Duration(i*250) * time.Millisecond)
|
|
}
|
|
}
|
|
return false
|
|
}()
|
|
require.True(t, done, "READY MESSAGE SHOULD HAVE ARRIVED")
|
|
}
|