package client import ( "context" "crypto/tls" "fmt" "net" "os" "strings" "testing" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/internal/testhelper" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/status" ) var proxyEnvironmentKeys = []string{"http_proxy", "https_proxy", "no_proxy"} func doDialAndExecuteCall(addr string) error { conn, err := Dial(addr, nil) if err != nil { return fmt.Errorf("dial: %v", err) } defer conn.Close() client := healthpb.NewHealthClient(conn) _, err = client.Check(context.Background(), &healthpb.HealthCheckRequest{}) return err } func TestDial(t *testing.T) { if emitProxyWarning() { t.Log("WARNING. Proxy configuration detected from environment settings. This test failure may be related to proxy configuration. Please process with caution") } stop, connectionMap, err := startListeners() require.NoError(t, err, "start listeners: %v. %s", err) defer stop() unixSocketAbsPath := connectionMap["unix"] unixSocketRelPath := "testdata/gitaly.socket" require.NoError(t, os.RemoveAll(unixSocketRelPath)) require.NoError(t, os.Symlink(unixSocketAbsPath, unixSocketRelPath)) tests := []struct { name string rawAddress string envSSLCertFile string expectFailure bool }{ { name: "tcp localhost with prefix", rawAddress: "tcp://localhost:" + connectionMap["tcp"], // "tcp://localhost:1234" expectFailure: false, }, { name: "tls localhost", rawAddress: "tls://localhost:" + connectionMap["tls"], // "tls://localhost:1234" envSSLCertFile: "./testdata/gitalycert.pem", expectFailure: false, }, { name: "unix absolute", rawAddress: "unix:" + unixSocketAbsPath, // "unix:/tmp/temp-socket" expectFailure: false, }, { name: "unix relative", rawAddress: "unix:" + unixSocketRelPath, // "unix:../../tmp/temp-socket" expectFailure: false, }, { name: "unix absolute does not exist", rawAddress: "unix:" + unixSocketAbsPath + ".does_not_exist", // "unix:/tmp/temp-socket.does_not_exist" expectFailure: true, }, { name: "unix relative does not exist", rawAddress: "unix:" + unixSocketRelPath + ".does_not_exist", // "unix:../../tmp/temp-socket.does_not_exist" expectFailure: true, }, { // Gitaly does not support connections that do not have a scheme. name: "tcp localhost no prefix", rawAddress: "localhost:" + connectionMap["tcp"], // "localhost:1234" expectFailure: true, }, { name: "invalid", rawAddress: ".", expectFailure: true, }, { name: "empty", rawAddress: "", expectFailure: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if emitProxyWarning() { t.Log("WARNING. Proxy configuration detected from environment settings. This test failure may be related to proxy configuration. Please process with caution") } if tt.envSSLCertFile != "" { restore := modifyEnvironment("SSL_CERT_FILE", tt.envSSLCertFile) defer restore() } err := doDialAndExecuteCall(tt.rawAddress) if tt.expectFailure { require.Error(t, err) return } require.NoError(t, err) }) } } // healthServer provide a basic GRPC health service endpoint for testing purposes type healthServer struct { } func (*healthServer) Check(context.Context, *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil } func (*healthServer) Watch(*healthpb.HealthCheckRequest, healthpb.Health_WatchServer) error { return status.Errorf(codes.Unimplemented, "Not implemented") } // startTCPListener will start a insecure TCP listener on a random unused port func startTCPListener() (func(), string, error) { listener, err := net.Listen("tcp", ":0") if err != nil { return nil, "", err } tcpPort := listener.Addr().(*net.TCPAddr).Port address := fmt.Sprintf("%d", tcpPort) grpcServer := grpc.NewServer() healthpb.RegisterHealthServer(grpcServer, &healthServer{}) go grpcServer.Serve(listener) return func() { grpcServer.Stop() }, address, nil } // startUnixListener will start a unix socket listener using a temporary file func startUnixListener() (func(), string, error) { serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() listener, err := net.Listen("unix", serverSocketPath) if err != nil { return nil, "", err } grpcServer := grpc.NewServer() healthpb.RegisterHealthServer(grpcServer, &healthServer{}) go grpcServer.Serve(listener) return func() { grpcServer.Stop() }, serverSocketPath, nil } // startTLSListener will start a secure TLS listener on a random unused port func startTLSListener() (func(), string, error) { listener, err := net.Listen("tcp", ":0") if err != nil { return nil, "", err } tcpPort := listener.Addr().(*net.TCPAddr).Port address := fmt.Sprintf("%d", tcpPort) cert, err := tls.LoadX509KeyPair("./testdata/gitalycert.pem", "./testdata/gitalykey.pem") if err != nil { return nil, "", err } grpcServer := grpc.NewServer(grpc.Creds(credentials.NewServerTLSFromCert(&cert))) healthpb.RegisterHealthServer(grpcServer, &healthServer{}) go grpcServer.Serve(listener) return func() { grpcServer.Stop() }, address, nil } var listeners = map[string]func() (func(), string, error){ "tcp": startTCPListener, "unix": startUnixListener, "tls": startTLSListener, } // startListeners will start all the different listeners used in this test func startListeners() (func(), map[string]string, error) { var closers []func() connectionMap := map[string]string{} for k, v := range listeners { closer, address, err := v() if err != nil { return nil, nil, err } closers = append(closers, closer) connectionMap[k] = address } return func() { for _, v := range closers { v() } }, connectionMap, nil } // modifyEnvironment will change an environment variable and return a func suitable // for `defer` to change the value back. func modifyEnvironment(key string, value string) func() { oldValue, hasOldValue := os.LookupEnv(key) os.Setenv(key, value) return func() { if hasOldValue { os.Setenv(key, oldValue) } else { os.Unsetenv(key) } } } func emitProxyWarning() bool { for _, key := range proxyEnvironmentKeys { value := os.Getenv(key) if value != "" { return true } value = os.Getenv(strings.ToUpper(key)) if value != "" { return true } } return false }