diff --git a/.gitignore b/.gitignore index 6db70a3..1d21693 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ go.work config.yaml +servicechecker +*.protoset diff --git a/Makefile b/Makefile index 2e9652a..e64e59e 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ all: - go build -o servicechecker ./cmd/servicechecker/*.go \ No newline at end of file + GOWORK=off go build -x -v -o servicechecker ./cmd/servicechecker/*.go \ No newline at end of file diff --git a/cmd/servicechecker/main.go b/cmd/servicechecker/main.go index ec171c0..bd24f0b 100644 --- a/cmd/servicechecker/main.go +++ b/cmd/servicechecker/main.go @@ -2,22 +2,113 @@ package main import ( "context" + "fmt" "os" + "time" + grpccli "go.unistack.org/micro-client-grpc/v3" + jsonpbcodec "go.unistack.org/micro-codec-jsonpb/v3" + protocodec "go.unistack.org/micro-codec-proto/v3" + victoriametrics "go.unistack.org/micro-meter-victoriametrics/v3" + "go.unistack.org/micro/v3/client" "go.unistack.org/micro/v3/logger/slog" + "go.unistack.org/micro/v3/semconv" "go.unistack.org/servicechecker/pkg/config" + "go.unistack.org/servicechecker/pkg/grpcconn" + "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/descriptorpb" + "google.golang.org/protobuf/types/dynamicpb" ) func main() { ctx := context.Background() - log := slog.NewLogger() + l := slog.NewLogger() + l.Init() + m := victoriametrics.NewMeter() f, err := os.Open("config.yaml") if err != nil { - log.Fatal(ctx, "failed to open config", err) + l.Fatal(ctx, "failed to open config", err) } defer f.Close() cfg := &config.Config{} if err = cfg.Parse(f); err != nil { - log.Fatal(ctx, "failed to open config", err) + l.Fatal(ctx, "failed to open config", err) } + + clients := make(map[string]client.Client) + gcli := grpccli.NewClient( + client.Codec("application/json", jsonpbcodec.NewCodec()), + client.Codec("application/grpc", protocodec.NewCodec()), + client.Codec("application/grpc+proto", protocodec.NewCodec()), + client.Codec("application/grpc+json", jsonpbcodec.NewCodec()), + client.ContentType("application/grpc"), + client.Retries(0), + // client.TLSConfig(&tls.Config{InsecureSkipVerify: true}), + ) + protosetBuf, _ := os.ReadFile("card.protoset") + fdset := &descriptorpb.FileDescriptorSet{} + if err = protocodec.NewCodec().Unmarshal(protosetBuf, fdset); err != nil { + l.Fatal(ctx, "failed to unmarshal protoset file", err) + } + + pfileoptions := protodesc.FileOptions{AllowUnresolvable: true} + pfiles, err := pfileoptions.NewFiles(fdset) + if err != nil { + l.Fatal(ctx, "failed to use protoset file", err) + } + + gcli.Init() + clients["grpc"] = gcli + + for _, svc := range cfg.Services { + c, ok := clients[svc.Type] + if !ok { + l.Error(ctx, fmt.Sprintf("unknown client %s", svc.Type)) + continue + } + pdesc, err := pfiles.FindDescriptorByName("card_proto.CardService") + if err != nil { + l.Error(ctx, "failed to find service "+svc.Name) + continue + } + + sdesc, ok := pdesc.(protoreflect.ServiceDescriptor) + if !ok { + l.Error(ctx, "failed to find service "+svc.Name) + continue + } + + for _, check := range svc.Checks { + mdesc := sdesc.Methods().ByName(protoreflect.Name(check.Name)) + if mdesc == nil { + l.Error(ctx, "failed to find method "+check.Name) + continue + } + + req := dynamicpb.NewMessageType(mdesc.Input()).New() + rsp := dynamicpb.NewMessageType(mdesc.Output()).New() + + if err = jsonpbcodec.NewCodec().Unmarshal([]byte(check.Data), req); err != nil { + l.Error(ctx, "failed to unmarshal", err) + continue + } + labels := []string{"service", svc.Name, "endpoint", check.Name} + m.Counter(semconv.ClientRequestInflight, labels...).Inc() + ts := time.Now() + err = grpcconn.Call(ctx, l, c, svc.Addr, time.Duration(check.Timeout), + c.NewRequest("card_proto", "CardService."+check.Name, req), + rsp) + te := time.Since(ts) + m.Summary(semconv.ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds()) + m.Histogram(semconv.ClientRequestDurationSeconds, labels...).Update(te.Seconds()) + m.Counter(semconv.ClientRequestInflight, labels...).Dec() + if err != nil { + m.Counter(semconv.ClientRequestTotal, append(labels, "status", "failure")...).Inc() + } else { + m.Counter(semconv.ClientRequestTotal, append(labels, "status", "success")...).Inc() + } + } + } + m.Write(os.Stdout) } diff --git a/go.mod b/go.mod index a274244..5ca05ca 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,27 @@ module go.unistack.org/servicechecker go 1.23.3 -require go.unistack.org/micro/v3 v3.10.100 +require ( + github.com/google/uuid v1.6.0 + github.com/jhump/protoreflect/v2 v2.0.0-beta.2 + go.unistack.org/micro-client-grpc/v3 v3.11.10 + go.unistack.org/micro-codec-jsonpb/v3 v3.10.3 + go.unistack.org/micro-codec-proto/v3 v3.10.2 + go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9 + go.unistack.org/micro-proto/v3 v3.4.1 + go.unistack.org/micro/v3 v3.10.100 + google.golang.org/protobuf v1.34.2 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/kr/text v0.2.0 // indirect + github.com/valyala/fastrand v1.1.0 // indirect + github.com/valyala/histogram v1.2.0 // indirect + go.unistack.org/metrics v0.0.1 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect +) diff --git a/go.sum b/go.sum index 0f1c4f5..97722a9 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,54 @@ -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jhump/protoreflect/v2 v2.0.0-beta.2 h1:qZU+rEZUOYTz1Bnhi3xbwn+VxdXkLVeEpAeZzVXLY88= +github.com/jhump/protoreflect/v2 v2.0.0-beta.2/go.mod h1:4tnOYkB/mq7QTyS3YKtVtNrJv4Psqout8HA1U+hZtgM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= +github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= +go.unistack.org/metrics v0.0.1 h1:sCnGO059ZccGC/D34iRH121eSk+7ci5+OY9cl5K7GKY= +go.unistack.org/metrics v0.0.1/go.mod h1:1FY4R7EKJa9Oz2D6wlGScNerpl6igRs9Cx/3et4Rgs4= +go.unistack.org/micro-client-grpc/v3 v3.11.10 h1:hbqCL4JLbTtPN1ee16EK6LqTbeIr6HynJJjCRWAu2kk= +go.unistack.org/micro-client-grpc/v3 v3.11.10/go.mod h1:S1AIA2n3GZVxKdjpMcLJSXwBpC/OzAX91D6KxFzqjbc= +go.unistack.org/micro-codec-jsonpb/v3 v3.10.3 h1:4GTNrhpwPCRqSuimlOdgViE+95IE4YeBNeOCTawSTeM= +go.unistack.org/micro-codec-jsonpb/v3 v3.10.3/go.mod h1:6avGT/PKXgZFh2d4dhyUfkKRWXPwzL2CJHnRLqx9o3g= +go.unistack.org/micro-codec-proto/v3 v3.10.2 h1:9iUQjBjTsd/RgIqB5rAQMZE0CYWngoW9pbrnfcFtbXM= +go.unistack.org/micro-codec-proto/v3 v3.10.2/go.mod h1:54e1jb6aLL9obJUwJjtVupE5zY4PugTcMSqWDhz9aC4= +go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9 h1:ZXCS0eFiSdvcFYxpxV2Q77gfwAjpIRydwAEI1QBrwuQ= +go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9/go.mod h1:xODJQ0Nu/F8k34D/z2ITL91OskI/C674XCkugAxmc3Q= +go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q= +go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44diiiLLsZ93Zo= go.unistack.org/micro/v3 v3.10.100 h1:yWOaU0ImCGm5k5MUzlIobJUOr+KLfrR/BoDZvcHyKxM= go.unistack.org/micro/v3 v3.10.100/go.mod h1:YzMldzHN9Ei+zy5t/Psu7RUWDZwUfrNYiStSQtTz90g= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/config/config.go b/pkg/config/config.go index 9fc5ece..3f7c442 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,22 +1,30 @@ package config -import "io" +import ( + "io" + + mtime "go.unistack.org/micro/v3/util/time" + yaml "gopkg.in/yaml.v3" +) type Config struct { Services []*Service `json:"services,omitempty" yaml:"services,omitempty"` } type Service struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Addr string `json:"addr,omitempty" yaml:"addr,omitempty"` - Checks []Check `json:"checks,omitempty" yaml:"checks,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Addr string `json:"addr,omitempty" yaml:"addr,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Reflection bool `json:"reflection,omitempty" yaml:"reflection,omitempty"` + Protoset string `json:"protoset,omitempty" yaml:"protoset,omitempty"` + Checks []*Check `json:"checks,omitempty" yaml:"checks,omitempty"` + TLSVerify *bool `json:"tls_verify,omitempty" yaml:"tls_verify,omitempty"` } type Check struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Type string `json:"type,omitempty" yaml:"type,omitempty"` - Data string `json:"data,omitempty" yaml:"data,omitempty"` - Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Data string `json:"data,omitempty" yaml:"data,omitempty"` + Timeout mtime.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"` } func (cfg *Config) Parse(r io.Reader) error { diff --git a/pkg/grpcconn/grpcconn.go b/pkg/grpcconn/grpcconn.go new file mode 100644 index 0000000..2071a57 --- /dev/null +++ b/pkg/grpcconn/grpcconn.go @@ -0,0 +1,30 @@ +package grpcconn + +import ( + "context" + "time" + + "github.com/google/uuid" + "go.unistack.org/micro/v3/client" + "go.unistack.org/micro/v3/logger" +) + +func Call(ctx context.Context, l logger.Logger, c client.Client, addr string, td time.Duration, req client.Request, rsp interface{}) error { + var err error + uid, err := uuid.NewRandom() + if err != nil { + l.Error(ctx, "failed to generate x-request-id", err) + return err + } + + err = c.Call(ctx, req, rsp, + client.WithAddress(addr), + client.WithRequestTimeout(td), + // client.WithContentType("application/json"), + ) + if err != nil { + l.Error(ctx, "call failed", "x-request-id", uid.String(), err) + return err + } + return nil +} diff --git a/pkg/grpcconn/protoset.go b/pkg/grpcconn/protoset.go new file mode 100644 index 0000000..b17b68c --- /dev/null +++ b/pkg/grpcconn/protoset.go @@ -0,0 +1,14 @@ +//go:build ignore + +package grpcconn + +import ( + "github.com/emicklei/proto" + "github.com/jhump/protoreflect/desc" +) + +var protoSets = map[string]*desc.FileDescriptor + +func GetMessage(service string, method string) (proto.Message, error) { + return nil, nil +}