1package checks 2 3import ( 4 "crypto/tls" 5 "flag" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "net" 10 "os" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/consul/agent/mock" 15 "github.com/hashicorp/consul/agent/structs" 16 "github.com/hashicorp/consul/api" 17 "github.com/hashicorp/consul/sdk/testutil/retry" 18 "github.com/hashicorp/go-hclog" 19 20 "google.golang.org/grpc" 21 "google.golang.org/grpc/health" 22 hv1 "google.golang.org/grpc/health/grpc_health_v1" 23) 24 25var ( 26 port int 27 server string 28 svcHealthy string 29 svcUnhealthy string 30 svcMissing string 31) 32 33func startServer() (*health.Server, *grpc.Server) { 34 listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 35 if err != nil { 36 log.Fatalf("failed to listen: %v", err) 37 } 38 grpcServer := grpc.NewServer() 39 server := health.NewServer() 40 hv1.RegisterHealthServer(grpcServer, server) 41 go grpcServer.Serve(listener) 42 return server, grpcServer 43} 44 45func init() { 46 flag.IntVar(&port, "grpc-stub-port", 54321, "port for the gRPC stub server") 47} 48 49func TestMain(m *testing.M) { 50 flag.Parse() 51 52 healthy := "healthy" 53 unhealthy := "unhealthy" 54 missing := "missing" 55 56 srv, grpcStubApp := startServer() 57 srv.SetServingStatus(healthy, hv1.HealthCheckResponse_SERVING) 58 srv.SetServingStatus(unhealthy, hv1.HealthCheckResponse_NOT_SERVING) 59 60 server = fmt.Sprintf("%s:%d", "localhost", port) 61 svcHealthy = fmt.Sprintf("%s/%s", server, healthy) 62 svcUnhealthy = fmt.Sprintf("%s/%s", server, unhealthy) 63 svcMissing = fmt.Sprintf("%s/%s", server, missing) 64 65 result := 1 66 defer func() { 67 grpcStubApp.Stop() 68 os.Exit(result) 69 }() 70 71 result = m.Run() 72} 73 74func TestCheck(t *testing.T) { 75 type args struct { 76 target string 77 timeout time.Duration 78 tlsConfig *tls.Config 79 } 80 tests := []struct { 81 name string 82 args args 83 healthy bool 84 }{ 85 // successes 86 {"should pass for healthy server", args{server, time.Second, nil}, true}, 87 {"should pass for healthy service", args{svcHealthy, time.Second, nil}, true}, 88 89 // failures 90 {"should fail for unhealthy service", args{svcUnhealthy, time.Second, nil}, false}, 91 {"should fail for missing service", args{svcMissing, time.Second, nil}, false}, 92 {"should timeout for healthy service", args{server, time.Nanosecond, nil}, false}, 93 {"should fail if probe is secure, but server is not", args{server, time.Second, &tls.Config{}}, false}, 94 } 95 for _, tt := range tests { 96 t.Run(tt.name, func(t *testing.T) { 97 probe := NewGrpcHealthProbe(tt.args.target, tt.args.timeout, tt.args.tlsConfig) 98 actualError := probe.Check(tt.args.target) 99 actuallyHealthy := actualError == nil 100 if tt.healthy != actuallyHealthy { 101 t.Errorf("FAIL: %s. Expected healthy %t, but err == %v", tt.name, tt.healthy, actualError) 102 } 103 }) 104 } 105} 106 107func TestGRPC_Proxied(t *testing.T) { 108 t.Parallel() 109 110 notif := mock.NewNotify() 111 logger := hclog.New(&hclog.LoggerOptions{ 112 Name: uniqueID(), 113 Output: ioutil.Discard, 114 }) 115 116 statusHandler := NewStatusHandler(notif, logger, 0, 0) 117 cid := structs.NewCheckID("foo", nil) 118 119 check := &CheckGRPC{ 120 CheckID: cid, 121 GRPC: "", 122 Interval: 10 * time.Millisecond, 123 Logger: logger, 124 ProxyGRPC: server, 125 StatusHandler: statusHandler, 126 } 127 check.Start() 128 defer check.Stop() 129 130 // If ProxyGRPC is set, check() reqs should go to that address 131 retry.Run(t, func(r *retry.R) { 132 if got, want := notif.Updates(cid), 2; got < want { 133 r.Fatalf("got %d updates want at least %d", got, want) 134 } 135 if got, want := notif.State(cid), api.HealthPassing; got != want { 136 r.Fatalf("got state %q want %q", got, want) 137 } 138 }) 139} 140 141func TestGRPC_NotProxied(t *testing.T) { 142 t.Parallel() 143 144 notif := mock.NewNotify() 145 logger := hclog.New(&hclog.LoggerOptions{ 146 Name: uniqueID(), 147 Output: ioutil.Discard, 148 }) 149 150 statusHandler := NewStatusHandler(notif, logger, 0, 0) 151 cid := structs.NewCheckID("foo", nil) 152 153 check := &CheckGRPC{ 154 CheckID: cid, 155 GRPC: server, 156 Interval: 10 * time.Millisecond, 157 Logger: logger, 158 ProxyGRPC: "", 159 StatusHandler: statusHandler, 160 } 161 check.Start() 162 defer check.Stop() 163 164 // If ProxyGRPC is not set, check() reqs should go to check.GRPC 165 retry.Run(t, func(r *retry.R) { 166 if got, want := notif.Updates(cid), 2; got < want { 167 r.Fatalf("got %d updates want at least %d", got, want) 168 } 169 if got, want := notif.State(cid), api.HealthPassing; got != want { 170 r.Fatalf("got state %q want %q", got, want) 171 } 172 }) 173} 174