github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/admin/http_over_uds_server_test.go (about) 1 //go:build !windows 2 // +build !windows 3 4 package admin_test 5 6 import ( 7 "fmt" 8 "net/http" 9 "os" 10 "time" 11 12 . "github.com/onsi/ginkgo/v2" 13 . "github.com/onsi/gomega" 14 "github.com/pyroscope-io/pyroscope/pkg/admin" 15 ) 16 17 type mockHandler struct{} 18 19 func (m mockHandler) ServeHTTP(http.ResponseWriter, *http.Request) {} 20 21 var fastTimeout = admin.WithTimeout(time.Millisecond * 1) 22 23 var _ = Describe("HTTP Over UDS", func() { 24 var ( 25 socketAddr string 26 dir string 27 cleanup func() 28 ) 29 30 When("passed an empty socket address", func() { 31 It("should give an error", func() { 32 httpClient, _ := admin.NewHTTPOverUDSClient("") 33 _, err := admin.NewUdsHTTPServer("", httpClient) 34 35 Expect(err).To(MatchError(admin.ErrInvalidSocketPathname)) 36 }) 37 }) 38 39 When("passed a non existing socket address", func() { 40 It("should give an error", func() { 41 // if user is root, s/he can create the socket anywhere 42 if os.Getuid() == 0 { 43 Skip("test is invalid when running as root") 44 } 45 46 socketAddr := "/non_existing_path" 47 _, err := admin.NewUdsHTTPServer(socketAddr, createHttpClientWithFastTimeout(socketAddr)) 48 49 // TODO how to test for wrapped errors? 50 // Expect(err).To(MatchError(fmt.Errorf("could not bind to socket"))) 51 Expect(err).To(HaveOccurred()) 52 }) 53 }) 54 55 When("passed an already bound socket address", func() { 56 BeforeEach(func() { 57 cleanup, dir = genRandomDir() 58 socketAddr = dir + "/pyroscope.sock" 59 }) 60 AfterEach(func() { 61 cleanup() 62 }) 63 64 When("that socket does not respond", func() { 65 It("should take over that socket", func() { 66 // create server 1 67 By("creating server 1 that's not running") 68 _, err := admin.NewUdsHTTPServer(socketAddr, createHttpClientWithFastTimeout(socketAddr)) 69 Expect(err).ToNot(HaveOccurred()) 70 71 By("creating server 2") 72 // create server 2 73 _, err = admin.NewUdsHTTPServer(socketAddr, createHttpClientWithFastTimeout(socketAddr)) 74 Expect(err).ToNot(HaveOccurred()) 75 }) 76 }) 77 78 When("that socket is still responding", func() { 79 It("should error", func() { 80 By("creating server 1 and running it") 81 server, err := admin.NewUdsHTTPServer(socketAddr, createHttpClientWithFastTimeout(socketAddr)) 82 Expect(err).ToNot(HaveOccurred()) 83 84 go func() { 85 server.Start(http.NewServeMux()) 86 }() 87 88 By("validating server 1 is responding") 89 Expect(waitUntilServerIsReady(socketAddr)).ToNot(HaveOccurred()) 90 91 // create server 2 92 By("creating server 2") 93 _, err = admin.NewUdsHTTPServer(socketAddr, createHttpClientWithFastTimeout(socketAddr)) 94 Expect(err).To(MatchError(admin.ErrSocketStillResponding)) 95 }) 96 }) 97 }) 98 99 When("server is closed", func() { 100 It("shutsdown properly", func() { 101 cleanup, dir = genRandomDir() 102 socketAddr = dir + "/pyroscope.sock" 103 defer cleanup() 104 105 // start the server 106 server, err := admin.NewUdsHTTPServer(socketAddr, createHttpClientWithFastTimeout(socketAddr)) 107 Expect(err).ToNot(HaveOccurred()) 108 go func() { 109 defer GinkgoRecover() 110 111 err := server.Start(http.NewServeMux()) 112 Expect(err).ToNot(HaveOccurred()) 113 }() 114 115 waitUntilServerIsReady(socketAddr) 116 117 err = server.Stop() 118 119 Expect(socketAddr).ToNot(BeAnExistingFile()) 120 Expect(err).ToNot(HaveOccurred()) 121 }) 122 }) 123 }) 124 125 func waitUntilServerIsReady(socketAddr string) error { 126 const MaxReadinessRetries = 30 // 3 seconds 127 128 client := createHttpClientWithFastTimeout(socketAddr) 129 retries := 0 130 131 for { 132 _, err := client.Get(admin.HealthAddress) 133 134 // all good? 135 if err == nil { 136 time.Sleep(time.Millisecond * 100) 137 return nil 138 } 139 if retries >= MaxReadinessRetries { 140 break 141 } 142 143 time.Sleep(time.Millisecond * 100) 144 retries++ 145 } 146 147 panic(fmt.Sprintf("maximum retries exceeded ('%d') waiting for server ('%s') to respond", retries, admin.HealthAddress)) 148 }