From 876e6d57663a1643f04d061024fd2a5b2f192c43 Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Sat, 27 Jun 2020 14:07:58 -0700 Subject: [PATCH 01/12] Add TLS support Signed-off-by: thatInfrastructureGuy --- types/services/routine.go | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/types/services/routine.go b/types/services/routine.go index 7a10fc21..84f2331b 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -4,9 +4,6 @@ import ( "bytes" "crypto/tls" "fmt" - "github.com/prometheus/client_golang/prometheus" - "github.com/statping/statping/types/metrics" - "google.golang.org/grpc" "net" "net/http" "net/url" @@ -14,6 +11,11 @@ import ( "strings" "time" + "github.com/prometheus/client_golang/prometheus" + "github.com/statping/statping/types/metrics" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "github.com/statping/statping/types/failures" "github.com/statping/statping/types/hits" "github.com/statping/statping/utils" @@ -115,6 +117,19 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { timer := prometheus.NewTimer(metrics.ServiceTimer(s.Name)) defer timer.ObserveDuration() + // Strip URL scheme if present. Eg: https:// , http:// + if strings.Contains(s.Domain, "://") { + u, err := url.Parse(s.Domain) + if err != nil { + // Unable to parse. + log.Warnln(fmt.Sprintf("GRPC Service: '%s', Unable to parse URL: '%v'", s.Name, s.Domain)) + } + + // Set domain as hostname without port number. + s.Domain = u.Hostname() + } + + // Calculate DNS check time dnsLookup, err := dnsCheck(s) if err != nil { if record { @@ -122,6 +137,18 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { } return s, err } + + // Connect to grpc service without TLS certs. + grpcOption := grpc.WithInsecure() + + // Check if TLS is enabled + // Upgrade GRPC connection if using TLS + // Force to connect on HTTP2 with TLS. Needed when using a reverse proxy such as nginx. + if s.VerifySSL.Bool { + h2creds := credentials.NewTLS(&tls.Config{NextProtos: []string{"h2"}}) + grpcOption = grpc.WithTransportCredentials(h2creds) + } + s.PingTime = dnsLookup t1 := utils.Now() domain := fmt.Sprintf("%v", s.Domain) @@ -131,7 +158,8 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { domain = fmt.Sprintf("[%v]:%v", s.Domain, s.Port) } } - conn, err := grpc.Dial(domain, grpc.WithInsecure(), grpc.WithBlock()) + + conn, err := grpc.Dial(domain, grpcOption, grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v", err) } @@ -147,6 +175,7 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { } return s, err } + s.Latency = utils.Now().Sub(t1).Microseconds() s.LastResponse = "" s.Online = true From 789443aab71909468e15bbdc19253745a0137d6a Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Sat, 22 Aug 2020 10:41:58 -0700 Subject: [PATCH 02/12] Check gRPC service via healthcheck Signed-off-by: thatInfrastructureGuy --- types/services/routine.go | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/types/services/routine.go b/types/services/routine.go index 84f2331b..41262a86 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -2,6 +2,7 @@ package services import ( "bytes" + "context" "crypto/tls" "fmt" "net" @@ -19,6 +20,7 @@ import ( "github.com/statping/statping/types/failures" "github.com/statping/statping/types/hits" "github.com/statping/statping/utils" + healthpb "google.golang.org/grpc/health/grpc_health_v1" ) // checkServices will start the checking go routine for each service @@ -169,6 +171,24 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { } return s, err } + + // Context will cancel the request when timeout is exceeded. + // Cancel the context when request is served within the timeout limit. + timeout := time.Duration(s.Timeout) * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + // Create a new health check client + c := healthpb.NewHealthClient(conn) + in := &healthpb.HealthCheckRequest{} + res, err := c.Check(ctx, in) + if err != nil { + if record { + recordFailure(s, fmt.Sprintf("GRPC Error %v", err)) + } + return s, nil + } + if err := conn.Close(); err != nil { if record { RecordFailure(s, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err), "close") @@ -176,12 +196,34 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { return s, err } + // Record latency and response s.Latency = utils.Now().Sub(t1).Microseconds() s.LastResponse = "" s.Online = true + s.LastResponse = res.String() + s.LastStatusCode = int(res.GetStatus()) + s.ExpectedStatus = 1 + s.Expected.String = "status:SERVING" + + if !(s.Expected.String == strings.TrimSpace(s.LastResponse)) { + log.Warnln(fmt.Sprintf("GRPC Service: '%s', Response: expected '%v', got '%v'", s.Name, s.LastResponse, s.Expected.String)) + if record { + recordFailure(s, fmt.Sprintf("GRPC Response Body did not match '%v'", s.Expected.String)) + } + return s, nil + } + + if s.ExpectedStatus != int(res.Status) { + if record { + recordFailure(s, fmt.Sprintf("GRPC Service: '%s', Status Code: expected '%v', got '%v'", s.Name, res.Status, healthpb.HealthCheckResponse_ServingStatus(s.ExpectedStatus))) + } + return s, nil + } + if record { RecordSuccess(s) } + return s, nil } From 7e3954e66e4535a6cd967bb1ecd98d3e08f2de29 Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Fri, 28 Aug 2020 11:54:32 -0700 Subject: [PATCH 03/12] Fix grpcCheck to RecordFailure Signed-off-by: thatInfrastructureGuy --- types/services/routine.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/types/services/routine.go b/types/services/routine.go index 41262a86..e7f0657f 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -184,7 +184,7 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { res, err := c.Check(ctx, in) if err != nil { if record { - recordFailure(s, fmt.Sprintf("GRPC Error %v", err)) + RecordFailure(s, fmt.Sprintf("GRPC Error %v", err), "healthcheck") } return s, nil } @@ -208,14 +208,14 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { if !(s.Expected.String == strings.TrimSpace(s.LastResponse)) { log.Warnln(fmt.Sprintf("GRPC Service: '%s', Response: expected '%v', got '%v'", s.Name, s.LastResponse, s.Expected.String)) if record { - recordFailure(s, fmt.Sprintf("GRPC Response Body did not match '%v'", s.Expected.String)) + RecordFailure(s, fmt.Sprintf("GRPC Response Body did not match '%v'", s.Expected.String), "response_body") } return s, nil } if s.ExpectedStatus != int(res.Status) { if record { - recordFailure(s, fmt.Sprintf("GRPC Service: '%s', Status Code: expected '%v', got '%v'", s.Name, res.Status, healthpb.HealthCheckResponse_ServingStatus(s.ExpectedStatus))) + RecordFailure(s, fmt.Sprintf("GRPC Service: '%s', Status Code: expected '%v', got '%v'", s.Name, res.Status, healthpb.HealthCheckResponse_ServingStatus(s.ExpectedStatus)), "response_code") } return s, nil } From 51e6ee2c63e9a689a8fa871a3629d7fbfe9a652e Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Fri, 28 Aug 2020 12:56:12 -0700 Subject: [PATCH 04/12] Ask to verify SSL for grpc services Signed-off-by: thatInfrastructureGuy --- frontend/src/forms/Service.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/forms/Service.vue b/frontend/src/forms/Service.vue index bd12e5d7..076b5d07 100644 --- a/frontend/src/forms/Service.vue +++ b/frontend/src/forms/Service.vue @@ -156,7 +156,7 @@ -
+
From 8f800e9b64c051dca74c6e9b171078e648c3521a Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Mon, 31 Aug 2020 16:29:11 -0700 Subject: [PATCH 05/12] Remove debug logs Signed-off-by: thatInfrastructureGuy --- types/services/routine.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/types/services/routine.go b/types/services/routine.go index e7f0657f..91a7a126 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -162,9 +162,6 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { } conn, err := grpc.Dial(domain, grpcOption, grpc.WithBlock()) - if err != nil { - log.Fatalf("did not connect: %v", err) - } if err != nil { if record { RecordFailure(s, fmt.Sprintf("Dial Error %v", err), "connection") From 24eeec99c9af5ae2b76036cf9d669656f8c7a70d Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Mon, 31 Aug 2020 17:19:33 -0700 Subject: [PATCH 06/12] Add timeout to grpc dial function call. Signed-off-by: thatInfrastructureGuy --- types/services/routine.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/types/services/routine.go b/types/services/routine.go index 91a7a126..924470c3 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -161,7 +161,13 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { } } - conn, err := grpc.Dial(domain, grpcOption, grpc.WithBlock()) + // Context will cancel the request when timeout is exceeded. + // Cancel the context when request is served within the timeout limit. + timeout := time.Duration(s.Timeout) * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + conn, err := grpc.DialContext(ctx, domain, grpcOption, grpc.WithBlock()) if err != nil { if record { RecordFailure(s, fmt.Sprintf("Dial Error %v", err), "connection") @@ -169,12 +175,6 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { return s, err } - // Context will cancel the request when timeout is exceeded. - // Cancel the context when request is served within the timeout limit. - timeout := time.Duration(s.Timeout) * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - // Create a new health check client c := healthpb.NewHealthClient(conn) in := &healthpb.HealthCheckRequest{} From 887f8cc9beb8dd52442613227ca2944b845edb02 Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Mon, 31 Aug 2020 18:02:43 -0700 Subject: [PATCH 07/12] Add tests for grpc checks Signed-off-by: thatInfrastructureGuy --- types/services/routine_test.go | 135 +++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 types/services/routine_test.go diff --git a/types/services/routine_test.go b/types/services/routine_test.go new file mode 100644 index 00000000..7027762c --- /dev/null +++ b/types/services/routine_test.go @@ -0,0 +1,135 @@ +package services + +import ( + "fmt" + "net" + "strconv" + "strings" + "testing" + + "github.com/statping/statping/types/null" + "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) + +// grpcServerDef is function type. +// Consumed by Test data. +type grpcServerDef func(int) *grpc.Server + +// Test Data: Simulates testing scenarios +var testdata = []struct { + grpcService grpcServerDef + clientChecker *Service +}{ + { + grpcService: func(port int) *grpc.Server { + return grpcServer(port, true) + }, + clientChecker: &Service{ + Name: "GRPC Server with Health check", + Domain: "localhost", + Port: 50053, + Expected: null.NewNullString("status:SERVING"), + ExpectedStatus: 1, + Type: "grpc", + Timeout: 3, + VerifySSL: null.NewNullBool(false), + }, + }, + { + grpcService: func(port int) *grpc.Server { + return grpcServer(port, true) + }, + clientChecker: &Service{ + Name: "Check TLS endpoint on GRPC Server with TLS disabled", + Domain: "localhost", + Port: 50054, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(true), + }, + }, + { + grpcService: func(port int) *grpc.Server { + return grpcServer(port, false) + }, + clientChecker: &Service{ + Name: "Check GRPC Server without Health check endpoint", + Domain: "localhost", + Port: 50055, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + }, + }, + { + grpcService: func(port int) *grpc.Server { + return grpcServer(50056, true) + }, + clientChecker: &Service{ + Name: "Check where no GRPC Server exists", + Domain: "localhost", + Port: 1000, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + }, + }, + { + grpcService: func(port int) *grpc.Server { + return grpcServer(50057, true) + }, + clientChecker: &Service{ + Name: "Check where no GRPC Server exists (Verify TLS)", + Domain: "localhost", + Port: 1000, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(true), + }, + }, +} + +// grpcServer creates grpc Service with optional parameters. +func grpcServer(port int, enableHealthCheck bool) *grpc.Server { + portString := strconv.Itoa(port) + server := grpc.NewServer() + lis, err := net.Listen("tcp", "localhost:"+portString) + if err != nil { + fmt.Println(err) + } + + if enableHealthCheck { + healthServer := health.NewServer() + healthServer.SetServingStatus("Test GRPC Service", healthpb.HealthCheckResponse_SERVING) + healthpb.RegisterHealthServer(server, healthServer) + go server.Serve(lis) + } + return server +} + +// TestCheckGrpc ranges over the testdata struct. +// Examines checkGrpc() function +func TestCheckGrpc(t *testing.T) { + for _, testscenario := range testdata { + v := testscenario + t.Run(v.clientChecker.Name, func(t *testing.T) { + t.Parallel() + server := v.grpcService(v.clientChecker.Port) + defer server.Stop() + v.clientChecker.CheckService(false) + if v.clientChecker.LastStatusCode != v.clientChecker.ExpectedStatus || strings.TrimSpace(v.clientChecker.LastResponse) != v.clientChecker.Expected.String { + t.Errorf("Expected message: '%v', Got message: '%v' , Expected Status: '%v', Got Status: '%v'", v.clientChecker.Expected, v.clientChecker.LastResponse, v.clientChecker.ExpectedStatus, v.clientChecker.LastStatusCode) + } + }) + } +} From a6df4e09c9ec40b3ef047e49962f2dfec701edc7 Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Mon, 31 Aug 2020 20:23:38 -0700 Subject: [PATCH 08/12] Add more test cases for grpc checks Signed-off-by: thatInfrastructureGuy --- types/services/routine.go | 3 +++ types/services/routine_test.go | 47 +++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/types/services/routine.go b/types/services/routine.go index 924470c3..98b6a000 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -125,6 +125,9 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { if err != nil { // Unable to parse. log.Warnln(fmt.Sprintf("GRPC Service: '%s', Unable to parse URL: '%v'", s.Name, s.Domain)) + if record { + RecordFailure(s, fmt.Sprintf("Unable to parse GRPC domain %v, %v", s.Domain, err), "parse_domain") + } } // Set domain as hostname without port number. diff --git a/types/services/routine_test.go b/types/services/routine_test.go index 7027762c..2228d5ce 100644 --- a/types/services/routine_test.go +++ b/types/services/routine_test.go @@ -97,6 +97,51 @@ var testdata = []struct { VerifySSL: null.NewNullBool(true), }, }, + { + grpcService: func(port int) *grpc.Server { + return grpcServer(port, true) + }, + clientChecker: &Service{ + Name: "Check GRPC Server with http:// url", + Domain: "http://localhost", + Port: 50058, + Expected: null.NewNullString("status:SERVING"), + ExpectedStatus: 1, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + }, + }, + { + grpcService: func(port int) *grpc.Server { + return grpcServer(port, true) + }, + clientChecker: &Service{ + Name: "Unparseable Url Error", + Domain: "http://local//host", + Port: 50059, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + }, + }, + { + grpcService: func(port int) *grpc.Server { + return grpcServer(50060, true) + }, + clientChecker: &Service{ + Name: "Check GRPC on HTTP server", + Domain: "https://google.com", + Port: 443, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + }, + }, } // grpcServer creates grpc Service with optional parameters. @@ -128,7 +173,7 @@ func TestCheckGrpc(t *testing.T) { defer server.Stop() v.clientChecker.CheckService(false) if v.clientChecker.LastStatusCode != v.clientChecker.ExpectedStatus || strings.TrimSpace(v.clientChecker.LastResponse) != v.clientChecker.Expected.String { - t.Errorf("Expected message: '%v', Got message: '%v' , Expected Status: '%v', Got Status: '%v'", v.clientChecker.Expected, v.clientChecker.LastResponse, v.clientChecker.ExpectedStatus, v.clientChecker.LastStatusCode) + t.Errorf("Expected message: '%v', Got message: '%v' , Expected Status: '%v', Got Status: '%v'", v.clientChecker.Expected.String, v.clientChecker.LastResponse, v.clientChecker.ExpectedStatus, v.clientChecker.LastStatusCode) } }) } From 77eb04fd67f722211113385ee42fd2ffe1ed4b2e Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Tue, 1 Sep 2020 01:43:06 -0700 Subject: [PATCH 09/12] Support grpc services without healthchecks Signed-off-by: thatInfrastructureGuy --- frontend/src/forms/Service.vue | 12 +++ types/services/routine.go | 54 +++++++----- types/services/routine_test.go | 154 +++++++++++++++++++-------------- types/services/struct.go | 4 +- 4 files changed, 133 insertions(+), 91 deletions(-) diff --git a/frontend/src/forms/Service.vue b/frontend/src/forms/Service.vue index 076b5d07..a4b9545d 100644 --- a/frontend/src/forms/Service.vue +++ b/frontend/src/forms/Service.vue @@ -167,6 +167,17 @@
+
+ +
+ + + + + +
+
+
@@ -276,6 +287,7 @@ permalink: "", order: 1, verify_ssl: true, + grpc_health_check: false, redirect: true, allow_notifications: true, notify_all_changes: true, diff --git a/types/services/routine.go b/types/services/routine.go index 98b6a000..9d889483 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -14,6 +14,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/statping/statping/types/metrics" + "github.com/statping/statping/types/null" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -178,15 +179,23 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { return s, err } - // Create a new health check client - c := healthpb.NewHealthClient(conn) - in := &healthpb.HealthCheckRequest{} - res, err := c.Check(ctx, in) - if err != nil { - if record { - RecordFailure(s, fmt.Sprintf("GRPC Error %v", err), "healthcheck") + if s.GrpcHealthCheck.Bool { + // Create a new health check client + c := healthpb.NewHealthClient(conn) + in := &healthpb.HealthCheckRequest{} + res, err := c.Check(ctx, in) + if err != nil { + if record { + RecordFailure(s, fmt.Sprintf("GRPC Error %v", err), "healthcheck") + } + return s, nil } - return s, nil + + // Record responses + s.ExpectedStatus = 1 + s.Expected = null.NewNullString("status:SERVING") + s.LastResponse = res.String() + s.LastStatusCode = int(res.GetStatus()) } if err := conn.Close(); err != nil { @@ -196,28 +205,25 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { return s, err } - // Record latency and response + // Record latency s.Latency = utils.Now().Sub(t1).Microseconds() - s.LastResponse = "" s.Online = true - s.LastResponse = res.String() - s.LastStatusCode = int(res.GetStatus()) - s.ExpectedStatus = 1 - s.Expected.String = "status:SERVING" - if !(s.Expected.String == strings.TrimSpace(s.LastResponse)) { - log.Warnln(fmt.Sprintf("GRPC Service: '%s', Response: expected '%v', got '%v'", s.Name, s.LastResponse, s.Expected.String)) - if record { - RecordFailure(s, fmt.Sprintf("GRPC Response Body did not match '%v'", s.Expected.String), "response_body") + if s.GrpcHealthCheck.Bool { + if s.ExpectedStatus != s.LastStatusCode { + if record { + RecordFailure(s, fmt.Sprintf("GRPC Service: '%s', Status Code: expected '%v', got '%v'", s.Name, s.ExpectedStatus, s.LastStatusCode), "response_code") + } + return s, nil } - return s, nil - } - if s.ExpectedStatus != int(res.Status) { - if record { - RecordFailure(s, fmt.Sprintf("GRPC Service: '%s', Status Code: expected '%v', got '%v'", s.Name, res.Status, healthpb.HealthCheckResponse_ServingStatus(s.ExpectedStatus)), "response_code") + if s.Expected.String != strings.TrimSpace(s.LastResponse) { + log.Warnln(fmt.Sprintf("GRPC Service: '%s', Response: expected '%v', got '%v'", s.Name, s.Expected.String, s.LastResponse)) + if record { + RecordFailure(s, fmt.Sprintf("GRPC Response Body '%v' did not match '%v'", s.LastResponse, s.Expected.String), "response_body") + } + return s, nil } - return s, nil } if record { diff --git a/types/services/routine_test.go b/types/services/routine_test.go index 2228d5ce..92f1035c 100644 --- a/types/services/routine_test.go +++ b/types/services/routine_test.go @@ -15,7 +15,7 @@ import ( // grpcServerDef is function type. // Consumed by Test data. -type grpcServerDef func(int) *grpc.Server +type grpcServerDef func(int, bool) *grpc.Server // Test Data: Simulates testing scenarios var testdata = []struct { @@ -23,38 +23,40 @@ var testdata = []struct { clientChecker *Service }{ { - grpcService: func(port int) *grpc.Server { - return grpcServer(port, true) + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(port, enableHealthCheck) }, clientChecker: &Service{ - Name: "GRPC Server with Health check", - Domain: "localhost", - Port: 50053, - Expected: null.NewNullString("status:SERVING"), - ExpectedStatus: 1, - Type: "grpc", - Timeout: 3, - VerifySSL: null.NewNullBool(false), + Name: "GRPC Server with Health check", + Domain: "localhost", + Port: 50053, + Expected: null.NewNullString("status:SERVING"), + ExpectedStatus: 1, + Type: "grpc", + Timeout: 3, + VerifySSL: null.NewNullBool(false), + GrpcHealthCheck: null.NewNullBool(true), }, }, { - grpcService: func(port int) *grpc.Server { - return grpcServer(port, true) + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(port, enableHealthCheck) }, clientChecker: &Service{ - Name: "Check TLS endpoint on GRPC Server with TLS disabled", - Domain: "localhost", - Port: 50054, - Expected: null.NewNullString(""), - ExpectedStatus: 0, - Type: "grpc", - Timeout: 1, - VerifySSL: null.NewNullBool(true), + Name: "Check TLS endpoint on GRPC Server with TLS disabled", + Domain: "localhost", + Port: 50054, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(true), + GrpcHealthCheck: null.NewNullBool(true), }, }, { - grpcService: func(port int) *grpc.Server { - return grpcServer(port, false) + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(port, enableHealthCheck) }, clientChecker: &Service{ Name: "Check GRPC Server without Health check endpoint", @@ -68,68 +70,72 @@ var testdata = []struct { }, }, { - grpcService: func(port int) *grpc.Server { - return grpcServer(50056, true) + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(50056, enableHealthCheck) }, clientChecker: &Service{ - Name: "Check where no GRPC Server exists", - Domain: "localhost", - Port: 1000, - Expected: null.NewNullString(""), - ExpectedStatus: 0, - Type: "grpc", - Timeout: 1, - VerifySSL: null.NewNullBool(false), + Name: "Check where no GRPC Server exists", + Domain: "localhost", + Port: 1000, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + GrpcHealthCheck: null.NewNullBool(true), }, }, { - grpcService: func(port int) *grpc.Server { - return grpcServer(50057, true) + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(50057, enableHealthCheck) }, clientChecker: &Service{ - Name: "Check where no GRPC Server exists (Verify TLS)", - Domain: "localhost", - Port: 1000, - Expected: null.NewNullString(""), - ExpectedStatus: 0, - Type: "grpc", - Timeout: 1, - VerifySSL: null.NewNullBool(true), + Name: "Check where no GRPC Server exists (Verify TLS)", + Domain: "localhost", + Port: 1000, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(true), + GrpcHealthCheck: null.NewNullBool(true), }, }, { - grpcService: func(port int) *grpc.Server { - return grpcServer(port, true) + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(port, enableHealthCheck) }, clientChecker: &Service{ - Name: "Check GRPC Server with http:// url", - Domain: "http://localhost", - Port: 50058, - Expected: null.NewNullString("status:SERVING"), - ExpectedStatus: 1, - Type: "grpc", - Timeout: 1, - VerifySSL: null.NewNullBool(false), + Name: "Check GRPC Server with url", + Domain: "http://localhost", + Port: 50058, + Expected: null.NewNullString("status:SERVING"), + ExpectedStatus: 1, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + GrpcHealthCheck: null.NewNullBool(true), }, }, { - grpcService: func(port int) *grpc.Server { - return grpcServer(port, true) + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(port, enableHealthCheck) }, clientChecker: &Service{ - Name: "Unparseable Url Error", - Domain: "http://local//host", - Port: 50059, - Expected: null.NewNullString(""), - ExpectedStatus: 0, - Type: "grpc", - Timeout: 1, - VerifySSL: null.NewNullBool(false), + Name: "Unparseable Url Error", + Domain: "http://local//host", + Port: 50059, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + GrpcHealthCheck: null.NewNullBool(true), }, }, { - grpcService: func(port int) *grpc.Server { - return grpcServer(50060, true) + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(50060, enableHealthCheck) }, clientChecker: &Service{ Name: "Check GRPC on HTTP server", @@ -142,6 +148,22 @@ var testdata = []struct { VerifySSL: null.NewNullBool(false), }, }, + { + grpcService: func(port int, enableHealthCheck bool) *grpc.Server { + return grpcServer(port, true) + }, + clientChecker: &Service{ + Name: "GRPC HealthCheck where health check endpoint is not implemented", + Domain: "http://localhost", + Port: 50061, + Expected: null.NewNullString(""), + ExpectedStatus: 0, + Type: "grpc", + Timeout: 1, + VerifySSL: null.NewNullBool(false), + GrpcHealthCheck: null.NewNullBool(false), + }, + }, } // grpcServer creates grpc Service with optional parameters. @@ -169,7 +191,7 @@ func TestCheckGrpc(t *testing.T) { v := testscenario t.Run(v.clientChecker.Name, func(t *testing.T) { t.Parallel() - server := v.grpcService(v.clientChecker.Port) + server := v.grpcService(v.clientChecker.Port, v.clientChecker.GrpcHealthCheck.Bool) defer server.Stop() v.clientChecker.CheckService(false) if v.clientChecker.LastStatusCode != v.clientChecker.ExpectedStatus || strings.TrimSpace(v.clientChecker.LastResponse) != v.clientChecker.Expected.String { diff --git a/types/services/struct.go b/types/services/struct.go index 472614d3..ca55aaba 100644 --- a/types/services/struct.go +++ b/types/services/struct.go @@ -1,12 +1,13 @@ package services import ( + "time" + "github.com/statping/statping/types/checkins" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/incidents" "github.com/statping/statping/types/messages" "github.com/statping/statping/types/null" - "time" ) // Service is the main struct for Services @@ -24,6 +25,7 @@ type Service struct { Timeout int `gorm:"default:30;column:timeout" json:"timeout" scope:"user,admin" yaml:"timeout"` Order int `gorm:"default:0;column:order_id" json:"order_id" yaml:"order_id"` VerifySSL null.NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl" scope:"user,admin" yaml:"verify_ssl"` + GrpcHealthCheck null.NullBool `gorm:"default:false;column:grpc_health_check" json:"grpc_health_check" scope:"user,admin" yaml:"grpc_health_check"` Public null.NullBool `gorm:"default:true;column:public" json:"public" yaml:"public"` GroupId int `gorm:"default:0;column:group_id" json:"group_id" yaml:"group_id"` TLSCert null.NullString `gorm:"column:tls_cert" json:"tls_cert" scope:"user,admin" yaml:"tls_cert"` From f0f50089824ccf48b342a81a5203d295aa301cc8 Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Tue, 1 Sep 2020 09:32:14 -0700 Subject: [PATCH 10/12] Add doc link to GRPC Health Check toggle Signed-off-by: thatInfrastructureGuy --- frontend/src/forms/Service.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/forms/Service.vue b/frontend/src/forms/Service.vue index a4b9545d..dd547361 100644 --- a/frontend/src/forms/Service.vue +++ b/frontend/src/forms/Service.vue @@ -168,7 +168,7 @@
- +
From 3ea70439abbc238c7f187ef462b89ea4bb423f98 Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Wed, 2 Sep 2020 10:14:22 -0700 Subject: [PATCH 11/12] Update default values for grpc check. Signed-off-by: thatInfrastructureGuy --- frontend/src/forms/Service.vue | 31 ++++++++++++++++++++++++++++++- types/services/routine.go | 7 ++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/frontend/src/forms/Service.vue b/frontend/src/forms/Service.vue index dd547361..c85bbe64 100644 --- a/frontend/src/forms/Service.vue +++ b/frontend/src/forms/Service.vue @@ -13,7 +13,7 @@
- @@ -178,6 +178,22 @@
+
+ +
+ + Check GPRC health check response codes for more information. +
+
+ +
+ +
+ + A status code of 1 is success, or view all the GRPC Status Codes +
+
+
@@ -328,6 +344,19 @@ this.service = this.in_service } this.use_tls = this.service.tls_cert !== "" + }, + updateDefaultValues() { + if (this.service.type === "grpc") { + this.service.expected_status = 1 + this.service.expected = "status:SERVING" + this.service.port = 50051 + this.service.verify_ssl = false + } else { + this.service.expected_status = 200 + this.service.expected = "" + this.service.port = 80 + this.service.verify_ssl = true + } }, updatePermalink() { const a = 'àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;' diff --git a/types/services/routine.go b/types/services/routine.go index 9d889483..50468945 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -14,7 +14,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/statping/statping/types/metrics" - "github.com/statping/statping/types/null" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -192,9 +191,7 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { } // Record responses - s.ExpectedStatus = 1 - s.Expected = null.NewNullString("status:SERVING") - s.LastResponse = res.String() + s.LastResponse = strings.TrimSpace(res.String()) s.LastStatusCode = int(res.GetStatus()) } @@ -217,7 +214,7 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { return s, nil } - if s.Expected.String != strings.TrimSpace(s.LastResponse) { + if s.Expected.String != s.LastResponse { log.Warnln(fmt.Sprintf("GRPC Service: '%s', Response: expected '%v', got '%v'", s.Name, s.Expected.String, s.LastResponse)) if record { RecordFailure(s, fmt.Sprintf("GRPC Response Body '%v' did not match '%v'", s.LastResponse, s.Expected.String), "response_body") From 904689bae4cc0699950290cd8e8d96ad5519460b Mon Sep 17 00:00:00 2001 From: thatInfrastructureGuy Date: Wed, 2 Sep 2020 14:44:18 -0700 Subject: [PATCH 12/12] Unset default method for grpc check. Signed-off-by: thatInfrastructureGuy --- frontend/src/forms/Service.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/forms/Service.vue b/frontend/src/forms/Service.vue index c85bbe64..8efa26a4 100644 --- a/frontend/src/forms/Service.vue +++ b/frontend/src/forms/Service.vue @@ -351,11 +351,13 @@ this.service.expected = "status:SERVING" this.service.port = 50051 this.service.verify_ssl = false + this.service.method = "" } else { this.service.expected_status = 200 this.service.expected = "" this.service.port = 80 this.service.verify_ssl = true + this.service.method = "GET" } }, updatePermalink() {