From ce4c96ae0a27c68acd4c3008cc2632ecdca2273d Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Sun, 28 Mar 2021 19:17:08 +0300 Subject: [PATCH] server/health: add health check handler Signed-off-by: Vasiliy Tolstov --- server/generate.go | 3 + server/health/health.go | 87 +++++++++++++++++++++++++++ server/health/health.proto | 62 +++++++++++++++++++ server/health/health_micro.pb.go | 38 ++++++++++++ server/health/health_micro_http.pb.go | 43 +++++++++++++ 5 files changed, 233 insertions(+) create mode 100644 server/generate.go create mode 100644 server/health/health.go create mode 100644 server/health/health.proto create mode 100644 server/health/health_micro.pb.go create mode 100644 server/health/health_micro_http.pb.go diff --git a/server/generate.go b/server/generate.go new file mode 100644 index 00000000..d235c559 --- /dev/null +++ b/server/generate.go @@ -0,0 +1,3 @@ +package server + +//go:generate protoc -I./health -I../ -I/home/vtolstov/.cache/go-path/pkg/mod/github.com/unistack-org/micro-proto@v0.0.1 --micro_out=components=micro|http|server,standalone=false,debug=true,paths=source_relative:./health health/health.proto diff --git a/server/health/health.go b/server/health/health.go new file mode 100644 index 00000000..a615919b --- /dev/null +++ b/server/health/health.go @@ -0,0 +1,87 @@ +package pb + +import ( + "context" + + "github.com/unistack-org/micro/v3/codec" + "github.com/unistack-org/micro/v3/errors" + "github.com/unistack-org/micro/v3/server" +) + +var ( + // guard to fail early + _ HealthServer = &handler{} +) + +type handler struct { + server server.Server + opts Options +} + +type CheckFunc func(context.Context) error + +type Option func(*Options) + +type Options struct { + LiveChecks []CheckFunc + ReadyChecks []CheckFunc + Version string + Name string +} + +func LiveChecks(fns ...CheckFunc) Option { + return func(o *Options) { + o.LiveChecks = append(o.LiveChecks, fns...) + } +} + +func ReadyChecks(fns ...CheckFunc) Option { + return func(o *Options) { + o.ReadyChecks = append(o.ReadyChecks, fns...) + } +} + +func Name(name string) Option { + return func(o *Options) { + o.Name = name + } +} + +func Version(version string) Option { + return func(o *Options) { + o.Version = version + } +} + +func NewHealth(opts ...Option) *handler { + options := Options{} + for _, o := range opts { + o(&options) + } + return &handler{opts: options} +} + +func (h *handler) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error { + var err error + for _, fn := range h.opts.LiveChecks { + if err = fn(ctx); err != nil { + return errors.ServiceUnavailable(h.opts.Name, "%v", err) + } + } + return nil +} + +func (h *handler) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error { + var err error + for _, fn := range h.opts.ReadyChecks { + if err = fn(ctx); err != nil { + return errors.ServiceUnavailable(h.opts.Name, "%v", err) + } + } + return nil +} + +func (h *handler) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error { + rsp.Data = []byte(h.opts.Version) + return nil +} diff --git a/server/health/health.proto b/server/health/health.proto new file mode 100644 index 00000000..86b822b0 --- /dev/null +++ b/server/health/health.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; + +package health; +option go_package = "github.com/unistack-org/micro/v3/server/health;pb"; + +import "api/annotations.proto"; +import "openapiv2/annotations.proto"; +import "codec/frame.proto"; + +service Health { + rpc Live(micro.codec.Frame) returns (micro.codec.Frame) { + option (micro.openapiv2.openapiv2_operation) = { + operation_id: "Live"; + responses: { + key: "default"; + value: { + description: "Error response"; + schema: { + json_schema: { + ref: "micro.codec.Frame"; + } + } + } + } + }; + option (micro.api.http) = { get: "/live"; }; + }; + rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) { + option (micro.openapiv2.openapiv2_operation) = { + operation_id: "Ready"; + responses: { + key: "default"; + value: { + description: "Error response"; + schema: { + json_schema: { + ref: "micro.codec.Frame"; + } + } + } + } + }; + option (micro.api.http) = { get: "/ready"; }; + }; + rpc Version(micro.codec.Frame) returns (micro.codec.Frame) { + option (micro.openapiv2.openapiv2_operation) = { + operation_id: "Version"; + responses: { + key: "default"; + value: { + description: "Error response"; + schema: { + json_schema: { + ref: "micro.codec.Frame"; + } + } + } + } + }; + option (micro.api.http) = { get: "/version"; }; + }; +}; diff --git a/server/health/health_micro.pb.go b/server/health/health_micro.pb.go new file mode 100644 index 00000000..ec483688 --- /dev/null +++ b/server/health/health_micro.pb.go @@ -0,0 +1,38 @@ +// Code generated by protoc-gen-micro +// source: health.proto +package pb + +import ( + context "context" + api "github.com/unistack-org/micro/v3/api" + codec "github.com/unistack-org/micro/v3/codec" +) + +func NewHealthEndpoints() []*api.Endpoint { + return []*api.Endpoint{ + &api.Endpoint{ + Name: "Health.Live", + Path: []string{"/live"}, + Method: []string{"GET"}, + Handler: "rpc", + }, + &api.Endpoint{ + Name: "Health.Ready", + Path: []string{"/ready"}, + Method: []string{"GET"}, + Handler: "rpc", + }, + &api.Endpoint{ + Name: "Health.Version", + Path: []string{"/version"}, + Method: []string{"GET"}, + Handler: "rpc", + }, + } +} + +type HealthServer interface { + Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error + Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error + Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error +} diff --git a/server/health/health_micro_http.pb.go b/server/health/health_micro_http.pb.go new file mode 100644 index 00000000..92f5f4ce --- /dev/null +++ b/server/health/health_micro_http.pb.go @@ -0,0 +1,43 @@ +// Code generated by protoc-gen-micro +// source: health.proto +package pb + +import ( + context "context" + api "github.com/unistack-org/micro/v3/api" + codec "github.com/unistack-org/micro/v3/codec" + server "github.com/unistack-org/micro/v3/server" +) + +type healthServer struct { + HealthServer +} + +func (h *healthServer) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error { + return h.HealthServer.Live(ctx, req, rsp) +} + +func (h *healthServer) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error { + return h.HealthServer.Ready(ctx, req, rsp) +} + +func (h *healthServer) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error { + return h.HealthServer.Version(ctx, req, rsp) +} + +func RegisterHealthServer(s server.Server, sh HealthServer, opts ...server.HandlerOption) error { + type health interface { + Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error + Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error + Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error + } + type Health struct { + health + } + h := &healthServer{sh} + var nopts []server.HandlerOption + for _, endpoint := range NewHealthEndpoints() { + nopts = append(nopts, api.WithEndpoint(endpoint)) + } + return s.Handle(s.NewHandler(&Health{h}, append(nopts, opts...)...)) +}