github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/notify_socket_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"net"
     7  	"testing"
     8  	"time"
     9  
    10  	"golang.org/x/sys/unix"
    11  )
    12  
    13  // TestNotifyHost tests how runc reports container readiness to the host (usually systemd).
    14  func TestNotifyHost(t *testing.T) {
    15  	addr := net.UnixAddr{
    16  		Name: t.TempDir() + "/testsocket",
    17  		Net:  "unixgram",
    18  	}
    19  
    20  	server, err := net.ListenUnixgram("unixgram", &addr)
    21  	if err != nil {
    22  		t.Fatal(err)
    23  	}
    24  	defer server.Close()
    25  
    26  	client, err := net.DialUnix("unixgram", nil, &addr)
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	defer client.Close()
    31  
    32  	// run notifyHost in a separate goroutine
    33  	notifyHostChan := make(chan error)
    34  	go func() {
    35  		notifyHostChan <- notifyHost(client, []byte("READY=42"), 1337)
    36  	}()
    37  
    38  	// mock a host process listening for runc's notifications
    39  	expectRead(t, server, "READY=42\n")
    40  	expectRead(t, server, "MAINPID=1337\n")
    41  	expectBarrier(t, server, notifyHostChan)
    42  }
    43  
    44  func expectRead(t *testing.T, r io.Reader, expected string) {
    45  	var buf [1024]byte
    46  	n, err := r.Read(buf[:])
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  	if !bytes.Equal(buf[:n], []byte(expected)) {
    51  		t.Fatalf("Expected to read '%s' but runc sent '%s' instead", expected, buf[:n])
    52  	}
    53  }
    54  
    55  func expectBarrier(t *testing.T, conn *net.UnixConn, notifyHostChan <-chan error) {
    56  	var msg, oob [1024]byte
    57  	n, oobn, _, _, err := conn.ReadMsgUnix(msg[:], oob[:])
    58  	if err != nil {
    59  		t.Fatal("Failed to receive BARRIER message", err)
    60  	}
    61  	if !bytes.Equal(msg[:n], []byte("BARRIER=1")) {
    62  		t.Fatalf("Expected to receive 'BARRIER=1' but got '%s' instead.", msg[:n])
    63  	}
    64  
    65  	fd := mustExtractFd(t, oob[:oobn])
    66  
    67  	// Test whether notifyHost actually honors the barrier
    68  	timer := time.NewTimer(500 * time.Millisecond)
    69  	select {
    70  	case <-timer.C:
    71  		// this is the expected case
    72  		break
    73  	case <-notifyHostChan:
    74  		t.Fatal("runc has terminated before barrier was lifted")
    75  	}
    76  
    77  	// Lift the barrier
    78  	err = unix.Close(fd)
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	// Expect notifyHost to terminate now
    84  	err = <-notifyHostChan
    85  	if err != nil {
    86  		t.Fatal("notifyHost function returned with error", err)
    87  	}
    88  }
    89  
    90  func mustExtractFd(t *testing.T, buf []byte) int {
    91  	cmsgs, err := unix.ParseSocketControlMessage(buf)
    92  	if err != nil {
    93  		t.Fatal("Failed to parse control message", err)
    94  	}
    95  
    96  	fd := 0
    97  	seenScmRights := false
    98  	for _, cmsg := range cmsgs {
    99  		if cmsg.Header.Type != unix.SCM_RIGHTS {
   100  			continue
   101  		}
   102  		if seenScmRights {
   103  			t.Fatal("Expected to see exactly one SCM_RIGHTS message, but got a second one")
   104  		}
   105  		seenScmRights = true
   106  		fds, err := unix.ParseUnixRights(&cmsg)
   107  		if err != nil {
   108  			t.Fatal("Failed to parse SCM_RIGHTS message", err)
   109  		}
   110  		if len(fds) != 1 {
   111  			t.Fatal("Expected to read exactly one file descriptor, but got", len(fds))
   112  		}
   113  		fd = fds[0]
   114  	}
   115  	if !seenScmRights {
   116  		t.Fatal("Control messages didn't contain an SCM_RIGHTS message")
   117  	}
   118  
   119  	return fd
   120  }