mirror of https://github.com/statping/statping
Support grpc services without healthchecks
Signed-off-by: thatInfrastructureGuy <thatInfrastructureGuy@gmail.com>pull/806/head
parent
a6df4e09c9
commit
77eb04fd67
|
@ -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…
Reference in New Issue