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 }