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  }