Support grpc services without healthchecks

Signed-off-by: thatInfrastructureGuy <thatInfrastructureGuy@gmail.com>
pull/806/head
thatInfrastructureGuy 4 years ago
parent a6df4e09c9
commit 77eb04fd67
No known key found for this signature in database
GPG Key ID: 3E9D4A7275BC5A6A

@ -167,6 +167,17 @@
</div>
</div>
<div v-if="service.type.match(/^(grpc)$/)" class="form-group row">
<label class="col-12 col-md-4 col-form-label">GRPC Health Check</label>
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
<span @click="service.grpc_health_check = !!service.grpc_health_check" class="switch float-left">
<input v-model="service.grpc_health_check" type="checkbox" name="grpc_health_check-option" class="switch" id="switch-grpc-health-check" v-bind:checked="service.grpc_health_check">
<label for="switch-grpc-health-check" v-if="service.grpc_health_check">Check against GRPC health check endpoint.</label>
<label for="switch-grpc-health-check" v-if="!service.grpc_health_check">Only checks if GRPC connection can be established.</label>
</span>
</div>
</div>
<div v-if="service.type.match(/^(tcp|http)$/)" class="form-group row">
<label class="col-12 col-md-4 col-form-label">Use TLS Certificate</label>
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
@ -276,6 +287,7 @@
permalink: "",
order: 1,
verify_ssl: true,
grpc_health_check: false,
redirect: true,
allow_notifications: true,
notify_all_changes: true,

@ -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 {

@ -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 {

@ -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"`

Loading…
Cancel
Save