Compare commits
39 Commits
v3.11.15
...
v3.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
f12473f4b1 | ||
|
724e2b5830 | ||
|
6bdf33c4ee | ||
|
84f52fd7ac | ||
|
6e30b53280 | ||
|
a60426c884 | ||
|
2998735bf3 | ||
|
3a96135df8 | ||
|
bf8b3aeac7 | ||
|
5a52b5929c | ||
|
0adb469a85 | ||
|
21004341bf | ||
|
cc26f2b8b1 | ||
|
1a6652fe6b | ||
|
d28f0670d6 | ||
|
7bdd619e1b | ||
|
c62d1d5eb8 | ||
|
d60d85de5c | ||
|
44f281f8d9 | ||
|
f698feac9c | ||
|
f55701b374 | ||
|
82e8298b73 | ||
|
fc54503232 | ||
|
6f0594eebe | ||
|
6b52f859cf | ||
|
a3d4b8f79b | ||
|
7c7df6b35d | ||
|
e80eab397a | ||
|
6cda6ef92e | ||
|
f9f61d29de | ||
|
1ae825032c | ||
|
f146b52418 | ||
|
78a79ca9e1 | ||
|
329bc2f265 | ||
|
8738ed7757 | ||
|
29e8cdbfe9 | ||
|
47f356fc5f | ||
|
21ffc73c4f | ||
|
81a9342b83 |
5
.github/workflows/pr.yml
vendored
5
.github/workflows/pr.yml
vendored
@@ -25,4 +25,7 @@ jobs:
|
||||
id: tests
|
||||
env:
|
||||
IN_TRAVIS_CI: yes
|
||||
run: go test -v ./...
|
||||
run: |
|
||||
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.1.4.linux-amd64.tgz | tar xvz
|
||||
cockroach-v20.1.4.linux-amd64/cockroach start-single-node --insecure &
|
||||
go test -v ./...
|
||||
|
17
.github/workflows/tests.yml
vendored
17
.github/workflows/tests.yml
vendored
@@ -7,13 +7,17 @@ jobs:
|
||||
name: Test repo
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.13
|
||||
id: go
|
||||
|
||||
- name: Setup Kind
|
||||
uses: engineerd/setup-kind@v0.4.0
|
||||
with:
|
||||
version: v0.8.1
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
@@ -26,9 +30,16 @@ jobs:
|
||||
env:
|
||||
IN_TRAVIS_CI: yes
|
||||
run: |
|
||||
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.1.4.linux-amd64.tgz | tar xvz
|
||||
kubectl apply -f runtime/kubernetes/test/test.yaml
|
||||
sudo mkdir -p /var/run/secrets/kubernetes.io/serviceaccount
|
||||
sudo chmod 777 /var/run/secrets/kubernetes.io/serviceaccount
|
||||
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.1.4.linux-amd64.tgz | tar -xvz
|
||||
cockroach-v20.1.4.linux-amd64/cockroach start-single-node --insecure &
|
||||
go test -v ./...
|
||||
wget -q https://github.com/nats-io/nats-streaming-server/releases/download/v0.18.0/nats-streaming-server-v0.18.0-linux-amd64.zip
|
||||
unzip ./nats-streaming-server-v0.18.0-linux-amd64.zip
|
||||
export PATH=$PATH:./nats-streaming-server-v0.18.0-linux-amd64
|
||||
nats-streaming-server &
|
||||
go test -tags kubernetes,nats -v ./...
|
||||
|
||||
- name: Notify of test failure
|
||||
if: failure()
|
||||
|
@@ -249,7 +249,7 @@ func requestPayload(r *http.Request) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
return raw.Marshal()
|
||||
case strings.Contains(ct, "application/www-x-form-urlencoded"):
|
||||
case strings.Contains(ct, "application/x-www-form-urlencoded"):
|
||||
r.ParseForm()
|
||||
|
||||
// generate a new set of values from the form
|
||||
|
@@ -1,268 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: api/service/proto/api.proto
|
||||
|
||||
package go_micro_api
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Endpoint struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Host []string `protobuf:"bytes,2,rep,name=host,proto3" json:"host,omitempty"`
|
||||
Path []string `protobuf:"bytes,3,rep,name=path,proto3" json:"path,omitempty"`
|
||||
Method []string `protobuf:"bytes,4,rep,name=method,proto3" json:"method,omitempty"`
|
||||
Stream bool `protobuf:"varint,5,opt,name=stream,proto3" json:"stream,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Endpoint) Reset() { *m = Endpoint{} }
|
||||
func (m *Endpoint) String() string { return proto.CompactTextString(m) }
|
||||
func (*Endpoint) ProtoMessage() {}
|
||||
func (*Endpoint) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c4a48b6b680b5c31, []int{0}
|
||||
}
|
||||
|
||||
func (m *Endpoint) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Endpoint.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Endpoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Endpoint.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Endpoint) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Endpoint.Merge(m, src)
|
||||
}
|
||||
func (m *Endpoint) XXX_Size() int {
|
||||
return xxx_messageInfo_Endpoint.Size(m)
|
||||
}
|
||||
func (m *Endpoint) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Endpoint.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Endpoint proto.InternalMessageInfo
|
||||
|
||||
func (m *Endpoint) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Endpoint) GetHost() []string {
|
||||
if m != nil {
|
||||
return m.Host
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Endpoint) GetPath() []string {
|
||||
if m != nil {
|
||||
return m.Path
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Endpoint) GetMethod() []string {
|
||||
if m != nil {
|
||||
return m.Method
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Endpoint) GetStream() bool {
|
||||
if m != nil {
|
||||
return m.Stream
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EmptyResponse struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EmptyResponse) Reset() { *m = EmptyResponse{} }
|
||||
func (m *EmptyResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*EmptyResponse) ProtoMessage() {}
|
||||
func (*EmptyResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c4a48b6b680b5c31, []int{1}
|
||||
}
|
||||
|
||||
func (m *EmptyResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EmptyResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EmptyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EmptyResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EmptyResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EmptyResponse.Merge(m, src)
|
||||
}
|
||||
func (m *EmptyResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_EmptyResponse.Size(m)
|
||||
}
|
||||
func (m *EmptyResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EmptyResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EmptyResponse proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Endpoint)(nil), "go.micro.api.Endpoint")
|
||||
proto.RegisterType((*EmptyResponse)(nil), "go.micro.api.EmptyResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("api/service/proto/api.proto", fileDescriptor_c4a48b6b680b5c31) }
|
||||
|
||||
var fileDescriptor_c4a48b6b680b5c31 = []byte{
|
||||
// 212 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0xd0, 0xc1, 0x4a, 0x03, 0x31,
|
||||
0x10, 0x80, 0x61, 0xd7, 0xad, 0x65, 0x1d, 0x14, 0x21, 0x87, 0x12, 0xec, 0x65, 0xd9, 0x53, 0x4f,
|
||||
0x59, 0xd0, 0x27, 0x28, 0xda, 0x17, 0xd8, 0x37, 0x88, 0xed, 0xd0, 0x9d, 0x43, 0x32, 0x43, 0x32,
|
||||
0x14, 0x7c, 0x08, 0xdf, 0x59, 0x12, 0x2b, 0x2c, 0x5e, 0xbd, 0xfd, 0xf3, 0x1d, 0x86, 0x61, 0x60,
|
||||
0xeb, 0x85, 0xc6, 0x8c, 0xe9, 0x42, 0x47, 0x1c, 0x25, 0xb1, 0xf2, 0xe8, 0x85, 0x5c, 0x2d, 0xf3,
|
||||
0x70, 0x66, 0x17, 0xe8, 0x98, 0xd8, 0x79, 0xa1, 0xe1, 0x02, 0xdd, 0x21, 0x9e, 0x84, 0x29, 0xaa,
|
||||
0x31, 0xb0, 0x8a, 0x3e, 0xa0, 0x6d, 0xfa, 0x66, 0x77, 0x3f, 0xd5, 0x2e, 0x36, 0x73, 0x56, 0x7b,
|
||||
0xdb, 0xb7, 0xc5, 0x4a, 0x17, 0x13, 0xaf, 0xb3, 0x6d, 0x7f, 0xac, 0xb4, 0xd9, 0xc0, 0x3a, 0xa0,
|
||||
0xce, 0x7c, 0xb2, 0xab, 0xaa, 0xd7, 0xa9, 0x78, 0xd6, 0x84, 0x3e, 0xd8, 0xbb, 0xbe, 0xd9, 0x75,
|
||||
0xd3, 0x75, 0x1a, 0x9e, 0xe0, 0xf1, 0x10, 0x44, 0x3f, 0x27, 0xcc, 0xc2, 0x31, 0xe3, 0xcb, 0x57,
|
||||
0x03, 0xed, 0x5e, 0xc8, 0xec, 0xa1, 0x9b, 0xf0, 0x4c, 0x59, 0x31, 0x99, 0x8d, 0x5b, 0xde, 0xea,
|
||||
0x7e, 0x0f, 0x7d, 0xde, 0xfe, 0xf1, 0xe5, 0xa2, 0xe1, 0xc6, 0xbc, 0x01, 0xbc, 0x63, 0xfa, 0xdf,
|
||||
0x92, 0x8f, 0x75, 0xfd, 0xd6, 0xeb, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x46, 0x62, 0x67, 0x30,
|
||||
0x4c, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// ApiClient is the client API for Api service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type ApiClient interface {
|
||||
Register(ctx context.Context, in *Endpoint, opts ...grpc.CallOption) (*EmptyResponse, error)
|
||||
Deregister(ctx context.Context, in *Endpoint, opts ...grpc.CallOption) (*EmptyResponse, error)
|
||||
}
|
||||
|
||||
type apiClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewApiClient(cc *grpc.ClientConn) ApiClient {
|
||||
return &apiClient{cc}
|
||||
}
|
||||
|
||||
func (c *apiClient) Register(ctx context.Context, in *Endpoint, opts ...grpc.CallOption) (*EmptyResponse, error) {
|
||||
out := new(EmptyResponse)
|
||||
err := c.cc.Invoke(ctx, "/go.micro.api.Api/Register", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *apiClient) Deregister(ctx context.Context, in *Endpoint, opts ...grpc.CallOption) (*EmptyResponse, error) {
|
||||
out := new(EmptyResponse)
|
||||
err := c.cc.Invoke(ctx, "/go.micro.api.Api/Deregister", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ApiServer is the server API for Api service.
|
||||
type ApiServer interface {
|
||||
Register(context.Context, *Endpoint) (*EmptyResponse, error)
|
||||
Deregister(context.Context, *Endpoint) (*EmptyResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedApiServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedApiServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedApiServer) Register(ctx context.Context, req *Endpoint) (*EmptyResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Register not implemented")
|
||||
}
|
||||
func (*UnimplementedApiServer) Deregister(ctx context.Context, req *Endpoint) (*EmptyResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Deregister not implemented")
|
||||
}
|
||||
|
||||
func RegisterApiServer(s *grpc.Server, srv ApiServer) {
|
||||
s.RegisterService(&_Api_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Api_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Endpoint)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ApiServer).Register(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/go.micro.api.Api/Register",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ApiServer).Register(ctx, req.(*Endpoint))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Api_Deregister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Endpoint)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ApiServer).Deregister(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/go.micro.api.Api/Deregister",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ApiServer).Deregister(ctx, req.(*Endpoint))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Api_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "go.micro.api.Api",
|
||||
HandlerType: (*ApiServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Register",
|
||||
Handler: _Api_Register_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Deregister",
|
||||
Handler: _Api_Deregister_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "api/service/proto/api.proto",
|
||||
}
|
@@ -1,110 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: api/service/proto/api.proto
|
||||
|
||||
package go_micro_api
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
import (
|
||||
context "context"
|
||||
api "github.com/micro/go-micro/v3/api"
|
||||
client "github.com/micro/go-micro/v3/client"
|
||||
server "github.com/micro/go-micro/v3/server"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ api.Endpoint
|
||||
var _ context.Context
|
||||
var _ client.Option
|
||||
var _ server.Option
|
||||
|
||||
// Api Endpoints for Api service
|
||||
|
||||
func NewApiEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Api service
|
||||
|
||||
type ApiService interface {
|
||||
Register(ctx context.Context, in *Endpoint, opts ...client.CallOption) (*EmptyResponse, error)
|
||||
Deregister(ctx context.Context, in *Endpoint, opts ...client.CallOption) (*EmptyResponse, error)
|
||||
}
|
||||
|
||||
type apiService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewApiService(name string, c client.Client) ApiService {
|
||||
return &apiService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *apiService) Register(ctx context.Context, in *Endpoint, opts ...client.CallOption) (*EmptyResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Api.Register", in)
|
||||
out := new(EmptyResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *apiService) Deregister(ctx context.Context, in *Endpoint, opts ...client.CallOption) (*EmptyResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Api.Deregister", in)
|
||||
out := new(EmptyResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Api service
|
||||
|
||||
type ApiHandler interface {
|
||||
Register(context.Context, *Endpoint, *EmptyResponse) error
|
||||
Deregister(context.Context, *Endpoint, *EmptyResponse) error
|
||||
}
|
||||
|
||||
func RegisterApiHandler(s server.Server, hdlr ApiHandler, opts ...server.HandlerOption) error {
|
||||
type api interface {
|
||||
Register(ctx context.Context, in *Endpoint, out *EmptyResponse) error
|
||||
Deregister(ctx context.Context, in *Endpoint, out *EmptyResponse) error
|
||||
}
|
||||
type Api struct {
|
||||
api
|
||||
}
|
||||
h := &apiHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Api{h}, opts...))
|
||||
}
|
||||
|
||||
type apiHandler struct {
|
||||
ApiHandler
|
||||
}
|
||||
|
||||
func (h *apiHandler) Register(ctx context.Context, in *Endpoint, out *EmptyResponse) error {
|
||||
return h.ApiHandler.Register(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *apiHandler) Deregister(ctx context.Context, in *Endpoint, out *EmptyResponse) error {
|
||||
return h.ApiHandler.Deregister(ctx, in, out)
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package go.micro.api;
|
||||
|
||||
service Api {
|
||||
rpc Register(Endpoint) returns (EmptyResponse) {};
|
||||
rpc Deregister(Endpoint) returns (EmptyResponse) {};
|
||||
}
|
||||
|
||||
message Endpoint {
|
||||
string name = 1;
|
||||
repeated string host = 2;
|
||||
repeated string path = 3;
|
||||
repeated string method = 4;
|
||||
bool stream = 5;
|
||||
}
|
||||
|
||||
message EmptyResponse {}
|
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
func TestBackoff(t *testing.T) {
|
||||
@@ -32,3 +34,63 @@ func TestBackoff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testRequest struct {
|
||||
service string
|
||||
method string
|
||||
endpoint string
|
||||
contentType string
|
||||
codec codec.Codec
|
||||
body interface{}
|
||||
opts RequestOptions
|
||||
}
|
||||
|
||||
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
|
||||
var opts RequestOptions
|
||||
|
||||
for _, o := range reqOpts {
|
||||
o(&opts)
|
||||
}
|
||||
|
||||
// set the content-type specified
|
||||
if len(opts.ContentType) > 0 {
|
||||
contentType = opts.ContentType
|
||||
}
|
||||
|
||||
return &testRequest{
|
||||
service: service,
|
||||
method: endpoint,
|
||||
endpoint: endpoint,
|
||||
body: request,
|
||||
contentType: contentType,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *testRequest) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
func (r *testRequest) Service() string {
|
||||
return r.service
|
||||
}
|
||||
|
||||
func (r *testRequest) Method() string {
|
||||
return r.method
|
||||
}
|
||||
|
||||
func (r *testRequest) Endpoint() string {
|
||||
return r.endpoint
|
||||
}
|
||||
|
||||
func (r *testRequest) Body() interface{} {
|
||||
return r.body
|
||||
}
|
||||
|
||||
func (r *testRequest) Codec() codec.Writer {
|
||||
return r.codec
|
||||
}
|
||||
|
||||
func (r *testRequest) Stream() bool {
|
||||
return r.opts.Stream
|
||||
}
|
||||
|
@@ -1,66 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/metadata"
|
||||
cache "github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
// NewCache returns an initialised cache.
|
||||
func NewCache() *Cache {
|
||||
return &Cache{
|
||||
cache: cache.New(cache.NoExpiration, 30*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for responses
|
||||
type Cache struct {
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
// Get a response from the cache
|
||||
func (c *Cache) Get(ctx context.Context, req Request) (interface{}, bool) {
|
||||
return c.cache.Get(key(ctx, req))
|
||||
}
|
||||
|
||||
// Set a response in the cache
|
||||
func (c *Cache) Set(ctx context.Context, req Request, rsp interface{}, expiry time.Duration) {
|
||||
c.cache.Set(key(ctx, req), rsp, expiry)
|
||||
}
|
||||
|
||||
// List the key value pairs in the cache
|
||||
func (c *Cache) List() map[string]string {
|
||||
items := c.cache.Items()
|
||||
|
||||
rsp := make(map[string]string, len(items))
|
||||
for k, v := range items {
|
||||
bytes, _ := json.Marshal(v.Object)
|
||||
rsp[k] = string(bytes)
|
||||
}
|
||||
|
||||
return rsp
|
||||
}
|
||||
|
||||
// key returns a hash for the context and request
|
||||
func key(ctx context.Context, req Request) string {
|
||||
ns, _ := metadata.Get(ctx, "Micro-Namespace")
|
||||
|
||||
bytes, _ := json.Marshal(map[string]interface{}{
|
||||
"namespace": ns,
|
||||
"request": map[string]interface{}{
|
||||
"service": req.Service(),
|
||||
"endpoint": req.Endpoint(),
|
||||
"method": req.Method(),
|
||||
"body": req.Body(),
|
||||
},
|
||||
})
|
||||
|
||||
h := fnv.New64()
|
||||
h.Write(bytes)
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/metadata"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
req := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar"}
|
||||
|
||||
t.Run("CacheMiss", func(t *testing.T) {
|
||||
if _, ok := NewCache().Get(ctx, req); ok {
|
||||
t.Errorf("Expected to get no result from Get")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CacheHit", func(t *testing.T) {
|
||||
c := NewCache()
|
||||
|
||||
rsp := "theresponse"
|
||||
c.Set(ctx, req, rsp, time.Minute)
|
||||
|
||||
if res, ok := c.Get(ctx, req); !ok {
|
||||
t.Errorf("Expected a result, got nothing")
|
||||
} else if res != rsp {
|
||||
t.Errorf("Expected '%v' result, got '%v'", rsp, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCacheKey(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
req1 := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar"}
|
||||
req2 := &testRequest{service: "go.micro.service.foo", method: "Foo.Baz"}
|
||||
req3 := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar", body: "customquery"}
|
||||
|
||||
t.Run("IdenticalRequests", func(t *testing.T) {
|
||||
key1 := key(ctx, req1)
|
||||
key2 := key(ctx, req1)
|
||||
if key1 != key2 {
|
||||
t.Errorf("Expected the keys to match for identical requests and context")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DifferentRequestEndpoints", func(t *testing.T) {
|
||||
key1 := key(ctx, req1)
|
||||
key2 := key(ctx, req2)
|
||||
|
||||
if key1 == key2 {
|
||||
t.Errorf("Expected the keys to differ for different request endpoints")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DifferentRequestBody", func(t *testing.T) {
|
||||
key1 := key(ctx, req2)
|
||||
key2 := key(ctx, req3)
|
||||
|
||||
if key1 == key2 {
|
||||
t.Errorf("Expected the keys to differ for different request bodies")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DifferentMetadata", func(t *testing.T) {
|
||||
mdCtx := metadata.Set(context.TODO(), "Micro-Namespace", "bar")
|
||||
key1 := key(mdCtx, req1)
|
||||
key2 := key(ctx, req1)
|
||||
|
||||
if key1 == key2 {
|
||||
t.Errorf("Expected the keys to differ for different metadata")
|
||||
}
|
||||
})
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type clientKey struct{}
|
||||
|
||||
func FromContext(ctx context.Context) (Client, bool) {
|
||||
c, ok := ctx.Value(clientKey{}).(Client)
|
||||
return c, ok
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context, c Client) context.Context {
|
||||
return context.WithValue(ctx, clientKey{}, c)
|
||||
}
|
@@ -19,16 +19,16 @@ func LookupRoute(ctx context.Context, req Request, opts CallOptions) ([]string,
|
||||
}
|
||||
|
||||
// construct the router query
|
||||
query := []router.QueryOption{router.QueryService(req.Service())}
|
||||
query := []router.LookupOption{}
|
||||
|
||||
// if a custom network was requested, pass this to the router. By default the router will use it's
|
||||
// own network, which is set during initialisation.
|
||||
if len(opts.Network) > 0 {
|
||||
query = append(query, router.QueryNetwork(opts.Network))
|
||||
query = append(query, router.LookupNetwork(opts.Network))
|
||||
}
|
||||
|
||||
// lookup the routes which can be used to execute the request
|
||||
routes, err := opts.Router.Lookup(query...)
|
||||
routes, err := opts.Router.Lookup(req.Service(), query...)
|
||||
if err == router.ErrRouteNotFound {
|
||||
return nil, errors.InternalServerError("go.micro.client", "service %s: %s", req.Service(), err.Error())
|
||||
} else if err != nil {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Package mucp provides an mucp client
|
||||
// Package mucp provides a transport agnostic RPC client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
raw "github.com/micro/go-micro/v3/codec/bytes"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/metadata"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/util/buf"
|
||||
"github.com/micro/go-micro/v3/util/pool"
|
||||
)
|
||||
|
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/micro/go-micro/v3/codec/proto"
|
||||
"github.com/micro/go-micro/v3/codec/protorpc"
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@@ -2,7 +2,7 @@ package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
type rpcResponse struct {
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
func TestCallOptions(t *testing.T) {
|
||||
|
@@ -7,13 +7,13 @@ import (
|
||||
"github.com/micro/go-micro/v3/broker"
|
||||
"github.com/micro/go-micro/v3/broker/http"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
thttp "github.com/micro/go-micro/v3/network/transport/http"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/router"
|
||||
regRouter "github.com/micro/go-micro/v3/router/registry"
|
||||
"github.com/micro/go-micro/v3/selector"
|
||||
"github.com/micro/go-micro/v3/selector/random"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
thttp "github.com/micro/go-micro/v3/transport/http"
|
||||
"github.com/micro/go-micro/v3/selector/roundrobin"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
@@ -36,9 +36,6 @@ type Options struct {
|
||||
PoolSize int
|
||||
PoolTTL time.Duration
|
||||
|
||||
// Response cache
|
||||
Cache *Cache
|
||||
|
||||
// Middleware for client
|
||||
Wrappers []Wrapper
|
||||
|
||||
@@ -55,8 +52,6 @@ type CallOptions struct {
|
||||
Address []string
|
||||
// Backoff func
|
||||
Backoff BackoffFunc
|
||||
// Duration to cache the response for
|
||||
CacheExpiry time.Duration
|
||||
// Transport Dial Timeout
|
||||
DialTimeout time.Duration
|
||||
// Number of Call attempts
|
||||
@@ -109,7 +104,6 @@ type RequestOptions struct {
|
||||
|
||||
func NewOptions(options ...Option) Options {
|
||||
opts := Options{
|
||||
Cache: NewCache(),
|
||||
Context: context.Background(),
|
||||
ContentType: "application/protobuf",
|
||||
Codecs: make(map[string]codec.NewCodec),
|
||||
@@ -125,7 +119,7 @@ func NewOptions(options ...Option) Options {
|
||||
PoolTTL: DefaultPoolTTL,
|
||||
Broker: http.NewBroker(),
|
||||
Router: regRouter.NewRouter(),
|
||||
Selector: random.NewSelector(),
|
||||
Selector: roundrobin.NewSelector(),
|
||||
Transport: thttp.NewTransport(),
|
||||
}
|
||||
|
||||
@@ -357,14 +351,6 @@ func WithAuthToken() CallOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithCache is a CallOption which sets the duration the response
|
||||
// shoull be cached for
|
||||
func WithCache(c time.Duration) CallOption {
|
||||
return func(o *CallOptions) {
|
||||
o.CacheExpiry = c
|
||||
}
|
||||
}
|
||||
|
||||
// WithNetwork is a CallOption which sets the network attribute
|
||||
func WithNetwork(n string) CallOption {
|
||||
return func(o *CallOptions) {
|
||||
|
@@ -1,402 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: client/service/proto/client.proto
|
||||
|
||||
package go_micro_client
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Request struct {
|
||||
Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
|
||||
Endpoint string `protobuf:"bytes,2,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
|
||||
ContentType string `protobuf:"bytes,3,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
|
||||
Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_27c3d425ddd1a066, []int{0}
|
||||
}
|
||||
|
||||
func (m *Request) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Request.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Request) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Request.Merge(m, src)
|
||||
}
|
||||
func (m *Request) XXX_Size() int {
|
||||
return xxx_messageInfo_Request.Size(m)
|
||||
}
|
||||
func (m *Request) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Request.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Request proto.InternalMessageInfo
|
||||
|
||||
func (m *Request) GetService() string {
|
||||
if m != nil {
|
||||
return m.Service
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetEndpoint() string {
|
||||
if m != nil {
|
||||
return m.Endpoint
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetContentType() string {
|
||||
if m != nil {
|
||||
return m.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Body []byte `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_27c3d425ddd1a066, []int{1}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
func (m *Response) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"`
|
||||
ContentType string `protobuf:"bytes,2,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
|
||||
Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Message) Reset() { *m = Message{} }
|
||||
func (m *Message) String() string { return proto.CompactTextString(m) }
|
||||
func (*Message) ProtoMessage() {}
|
||||
func (*Message) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_27c3d425ddd1a066, []int{2}
|
||||
}
|
||||
|
||||
func (m *Message) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Message.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Message.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Message) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Message.Merge(m, src)
|
||||
}
|
||||
func (m *Message) XXX_Size() int {
|
||||
return xxx_messageInfo_Message.Size(m)
|
||||
}
|
||||
func (m *Message) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Message.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Message proto.InternalMessageInfo
|
||||
|
||||
func (m *Message) GetTopic() string {
|
||||
if m != nil {
|
||||
return m.Topic
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Message) GetContentType() string {
|
||||
if m != nil {
|
||||
return m.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Message) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Request)(nil), "go.micro.client.Request")
|
||||
proto.RegisterType((*Response)(nil), "go.micro.client.Response")
|
||||
proto.RegisterType((*Message)(nil), "go.micro.client.Message")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("client/service/proto/client.proto", fileDescriptor_27c3d425ddd1a066) }
|
||||
|
||||
var fileDescriptor_27c3d425ddd1a066 = []byte{
|
||||
// 267 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x91, 0xc1, 0x4b, 0xc3, 0x30,
|
||||
0x14, 0xc6, 0x97, 0x6d, 0xb6, 0xf3, 0x39, 0x10, 0x1e, 0x1e, 0x62, 0x0f, 0xb2, 0xf5, 0xd4, 0x53,
|
||||
0x2b, 0x7a, 0x16, 0x0f, 0x3d, 0x0b, 0x52, 0xc5, 0xab, 0xb4, 0xd9, 0x63, 0x06, 0xba, 0x24, 0x36,
|
||||
0xd9, 0xa0, 0x7f, 0xa4, 0xff, 0x93, 0x90, 0x46, 0x27, 0xba, 0x5d, 0xbc, 0xe5, 0xfb, 0x7e, 0xe4,
|
||||
0x7b, 0x2f, 0x5f, 0x60, 0x29, 0x5a, 0x49, 0xca, 0x15, 0x96, 0xba, 0x9d, 0x14, 0x54, 0x98, 0x4e,
|
||||
0x3b, 0x5d, 0x0c, 0x66, 0xee, 0x05, 0x9e, 0xaf, 0x75, 0xbe, 0x91, 0xa2, 0xd3, 0xf9, 0x60, 0xa7,
|
||||
0x3b, 0x88, 0x2b, 0x7a, 0xdf, 0x92, 0x75, 0xc8, 0x21, 0x0e, 0x37, 0x39, 0x5b, 0xb0, 0xec, 0xb4,
|
||||
0xfa, 0x92, 0x98, 0xc0, 0x8c, 0xd4, 0xca, 0x68, 0xa9, 0x1c, 0x1f, 0x7b, 0xf4, 0xad, 0x71, 0x09,
|
||||
0x73, 0xa1, 0x95, 0x23, 0xe5, 0x5e, 0x5d, 0x6f, 0x88, 0x4f, 0x3c, 0x3f, 0x0b, 0xde, 0x73, 0x6f,
|
||||
0x08, 0x11, 0xa6, 0x8d, 0x5e, 0xf5, 0x7c, 0xba, 0x60, 0xd9, 0xbc, 0xf2, 0xe7, 0xf4, 0x0a, 0x66,
|
||||
0x15, 0x59, 0xa3, 0x95, 0xdd, 0x73, 0xf6, 0x83, 0xbf, 0x40, 0xfc, 0x40, 0xd6, 0xd6, 0x6b, 0xc2,
|
||||
0x0b, 0x38, 0x71, 0xda, 0x48, 0x11, 0xb6, 0x1a, 0xc4, 0x9f, 0xb9, 0xe3, 0xe3, 0x73, 0x27, 0xfb,
|
||||
0xdc, 0x9b, 0x0f, 0x06, 0x51, 0xe9, 0x9f, 0x8e, 0x77, 0x30, 0x2d, 0xeb, 0xb6, 0x45, 0x9e, 0xff,
|
||||
0x2a, 0x25, 0x0f, 0x8d, 0x24, 0x97, 0x07, 0xc8, 0xb0, 0x73, 0x3a, 0xc2, 0x12, 0xa2, 0x27, 0xd7,
|
||||
0x51, 0xbd, 0xf9, 0x67, 0x40, 0xc6, 0xae, 0x19, 0xde, 0x43, 0xfc, 0xb8, 0x6d, 0x5a, 0x69, 0xdf,
|
||||
0x0e, 0xa4, 0x84, 0x02, 0x92, 0xa3, 0x24, 0x1d, 0x35, 0x91, 0xff, 0xd7, 0xdb, 0xcf, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0xd6, 0x3f, 0xc3, 0xa1, 0xfc, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// ClientClient is the client API for Client service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type ClientClient interface {
|
||||
// Call allows a single request to be made
|
||||
Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
// Stream is a bidirectional stream
|
||||
Stream(ctx context.Context, opts ...grpc.CallOption) (Client_StreamClient, error)
|
||||
// Publish publishes a message and returns an empty Message
|
||||
Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error)
|
||||
}
|
||||
|
||||
type clientClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewClientClient(cc *grpc.ClientConn) ClientClient {
|
||||
return &clientClient{cc}
|
||||
}
|
||||
|
||||
func (c *clientClient) Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/go.micro.client.Client/Call", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clientClient) Stream(ctx context.Context, opts ...grpc.CallOption) (Client_StreamClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &_Client_serviceDesc.Streams[0], "/go.micro.client.Client/Stream", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &clientStreamClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Client_StreamClient interface {
|
||||
Send(*Request) error
|
||||
Recv() (*Response, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type clientStreamClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *clientStreamClient) Send(m *Request) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *clientStreamClient) Recv() (*Response, error) {
|
||||
m := new(Response)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *clientClient) Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) {
|
||||
out := new(Message)
|
||||
err := c.cc.Invoke(ctx, "/go.micro.client.Client/Publish", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ClientServer is the server API for Client service.
|
||||
type ClientServer interface {
|
||||
// Call allows a single request to be made
|
||||
Call(context.Context, *Request) (*Response, error)
|
||||
// Stream is a bidirectional stream
|
||||
Stream(Client_StreamServer) error
|
||||
// Publish publishes a message and returns an empty Message
|
||||
Publish(context.Context, *Message) (*Message, error)
|
||||
}
|
||||
|
||||
// UnimplementedClientServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedClientServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedClientServer) Call(ctx context.Context, req *Request) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Call not implemented")
|
||||
}
|
||||
func (*UnimplementedClientServer) Stream(srv Client_StreamServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Stream not implemented")
|
||||
}
|
||||
func (*UnimplementedClientServer) Publish(ctx context.Context, req *Message) (*Message, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Publish not implemented")
|
||||
}
|
||||
|
||||
func RegisterClientServer(s *grpc.Server, srv ClientServer) {
|
||||
s.RegisterService(&_Client_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Client_Call_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Request)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClientServer).Call(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/go.micro.client.Client/Call",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClientServer).Call(ctx, req.(*Request))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Client_Stream_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(ClientServer).Stream(&clientStreamServer{stream})
|
||||
}
|
||||
|
||||
type Client_StreamServer interface {
|
||||
Send(*Response) error
|
||||
Recv() (*Request, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type clientStreamServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *clientStreamServer) Send(m *Response) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *clientStreamServer) Recv() (*Request, error) {
|
||||
m := new(Request)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _Client_Publish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Message)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClientServer).Publish(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/go.micro.client.Client/Publish",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClientServer).Publish(ctx, req.(*Message))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Client_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "go.micro.client.Client",
|
||||
HandlerType: (*ClientServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Call",
|
||||
Handler: _Client_Call_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Publish",
|
||||
Handler: _Client_Publish_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Stream",
|
||||
Handler: _Client_Stream_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "client/service/proto/client.proto",
|
||||
}
|
@@ -1,215 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: client/service/proto/client.proto
|
||||
|
||||
package go_micro_client
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
import (
|
||||
context "context"
|
||||
api "github.com/micro/go-micro/v3/api"
|
||||
client "github.com/micro/go-micro/v3/client"
|
||||
server "github.com/micro/go-micro/v3/server"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ api.Endpoint
|
||||
var _ context.Context
|
||||
var _ client.Option
|
||||
var _ server.Option
|
||||
|
||||
// Api Endpoints for Client service
|
||||
|
||||
func NewClientEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Client service
|
||||
|
||||
type ClientService interface {
|
||||
// Call allows a single request to be made
|
||||
Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error)
|
||||
// Stream is a bidirectional stream
|
||||
Stream(ctx context.Context, opts ...client.CallOption) (Client_StreamService, error)
|
||||
// Publish publishes a message and returns an empty Message
|
||||
Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Message, error)
|
||||
}
|
||||
|
||||
type clientService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewClientService(name string, c client.Client) ClientService {
|
||||
return &clientService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientService) Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) {
|
||||
req := c.c.NewRequest(c.name, "Client.Call", in)
|
||||
out := new(Response)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clientService) Stream(ctx context.Context, opts ...client.CallOption) (Client_StreamService, error) {
|
||||
req := c.c.NewRequest(c.name, "Client.Stream", &Request{})
|
||||
stream, err := c.c.Stream(ctx, req, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientServiceStream{stream}, nil
|
||||
}
|
||||
|
||||
type Client_StreamService interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*Request) error
|
||||
Recv() (*Response, error)
|
||||
}
|
||||
|
||||
type clientServiceStream struct {
|
||||
stream client.Stream
|
||||
}
|
||||
|
||||
func (x *clientServiceStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *clientServiceStream) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *clientServiceStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *clientServiceStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *clientServiceStream) Send(m *Request) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *clientServiceStream) Recv() (*Response, error) {
|
||||
m := new(Response)
|
||||
err := x.stream.Recv(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *clientService) Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Message, error) {
|
||||
req := c.c.NewRequest(c.name, "Client.Publish", in)
|
||||
out := new(Message)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Client service
|
||||
|
||||
type ClientHandler interface {
|
||||
// Call allows a single request to be made
|
||||
Call(context.Context, *Request, *Response) error
|
||||
// Stream is a bidirectional stream
|
||||
Stream(context.Context, Client_StreamStream) error
|
||||
// Publish publishes a message and returns an empty Message
|
||||
Publish(context.Context, *Message, *Message) error
|
||||
}
|
||||
|
||||
func RegisterClientHandler(s server.Server, hdlr ClientHandler, opts ...server.HandlerOption) error {
|
||||
type client interface {
|
||||
Call(ctx context.Context, in *Request, out *Response) error
|
||||
Stream(ctx context.Context, stream server.Stream) error
|
||||
Publish(ctx context.Context, in *Message, out *Message) error
|
||||
}
|
||||
type Client struct {
|
||||
client
|
||||
}
|
||||
h := &clientHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Client{h}, opts...))
|
||||
}
|
||||
|
||||
type clientHandler struct {
|
||||
ClientHandler
|
||||
}
|
||||
|
||||
func (h *clientHandler) Call(ctx context.Context, in *Request, out *Response) error {
|
||||
return h.ClientHandler.Call(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *clientHandler) Stream(ctx context.Context, stream server.Stream) error {
|
||||
return h.ClientHandler.Stream(ctx, &clientStreamStream{stream})
|
||||
}
|
||||
|
||||
type Client_StreamStream interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*Response) error
|
||||
Recv() (*Request, error)
|
||||
}
|
||||
|
||||
type clientStreamStream struct {
|
||||
stream server.Stream
|
||||
}
|
||||
|
||||
func (x *clientStreamStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *clientStreamStream) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *clientStreamStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *clientStreamStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *clientStreamStream) Send(m *Response) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *clientStreamStream) Recv() (*Request, error) {
|
||||
m := new(Request)
|
||||
if err := x.stream.Recv(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (h *clientHandler) Publish(ctx context.Context, in *Message, out *Message) error {
|
||||
return h.ClientHandler.Publish(ctx, in, out)
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package go.micro.client;
|
||||
|
||||
// Client is the micro client interface
|
||||
service Client {
|
||||
// Call allows a single request to be made
|
||||
rpc Call(Request) returns (Response) {};
|
||||
// Stream is a bidirectional stream
|
||||
rpc Stream(stream Request) returns (stream Response) {};
|
||||
// Publish publishes a message and returns an empty Message
|
||||
rpc Publish(Message) returns (Message) {};
|
||||
}
|
||||
|
||||
message Request {
|
||||
string service = 1;
|
||||
string endpoint = 2;
|
||||
string content_type = 3;
|
||||
bytes body = 4;
|
||||
}
|
||||
|
||||
message Response {
|
||||
bytes body = 1;
|
||||
}
|
||||
|
||||
message Message {
|
||||
string topic = 1;
|
||||
string content_type = 2;
|
||||
bytes body = 3;
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
service string
|
||||
method string
|
||||
endpoint string
|
||||
contentType string
|
||||
codec codec.Codec
|
||||
body interface{}
|
||||
opts RequestOptions
|
||||
}
|
||||
|
||||
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
|
||||
var opts RequestOptions
|
||||
|
||||
for _, o := range reqOpts {
|
||||
o(&opts)
|
||||
}
|
||||
|
||||
// set the content-type specified
|
||||
if len(opts.ContentType) > 0 {
|
||||
contentType = opts.ContentType
|
||||
}
|
||||
|
||||
return &testRequest{
|
||||
service: service,
|
||||
method: endpoint,
|
||||
endpoint: endpoint,
|
||||
body: request,
|
||||
contentType: contentType,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *testRequest) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
func (r *testRequest) Service() string {
|
||||
return r.service
|
||||
}
|
||||
|
||||
func (r *testRequest) Method() string {
|
||||
return r.method
|
||||
}
|
||||
|
||||
func (r *testRequest) Endpoint() string {
|
||||
return r.endpoint
|
||||
}
|
||||
|
||||
func (r *testRequest) Body() interface{} {
|
||||
return r.body
|
||||
}
|
||||
|
||||
func (r *testRequest) Codec() codec.Writer {
|
||||
return r.codec
|
||||
}
|
||||
|
||||
func (r *testRequest) Stream() bool {
|
||||
return r.opts.Stream
|
||||
}
|
@@ -1,7 +1,2 @@
|
||||
// Package debug provides micro debug packages
|
||||
// Package debug provides interfaces for service debugging
|
||||
package debug
|
||||
|
||||
var (
|
||||
// DefaultName is the name of debug service
|
||||
DefaultName = "go.micro.debug"
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Package events contains interfaces for managing events within distributed systems
|
||||
// Package events is for event streaming and storage
|
||||
package events
|
||||
|
||||
import (
|
||||
@@ -14,18 +14,21 @@ var (
|
||||
ErrEncodingMessage = errors.New("Error encoding message")
|
||||
)
|
||||
|
||||
// Stream of events
|
||||
// Stream is an event streaming interface
|
||||
type Stream interface {
|
||||
Publish(topic string, opts ...PublishOption) error
|
||||
Subscribe(opts ...SubscribeOption) (<-chan Event, error)
|
||||
Publish(topic string, msg interface{}, opts ...PublishOption) error
|
||||
Subscribe(topic string, opts ...SubscribeOption) (<-chan Event, error)
|
||||
}
|
||||
|
||||
// Store of events
|
||||
// Store is an event store interface
|
||||
type Store interface {
|
||||
Read(opts ...ReadOption) ([]*Event, error)
|
||||
Read(topic string, opts ...ReadOption) ([]*Event, error)
|
||||
Write(event *Event, opts ...WriteOption) error
|
||||
}
|
||||
|
||||
type AckFunc func() error
|
||||
type NackFunc func() error
|
||||
|
||||
// Event is the object returned by the broker when you subscribe to a topic
|
||||
type Event struct {
|
||||
// ID to uniquely identify the event
|
||||
@@ -34,13 +37,34 @@ type Event struct {
|
||||
Topic string
|
||||
// Timestamp of the event
|
||||
Timestamp time.Time
|
||||
// Metadata contains the encoded event was indexed by
|
||||
// Metadata contains the values the event was indexed by
|
||||
Metadata map[string]string
|
||||
// Payload contains the encoded message
|
||||
Payload []byte
|
||||
|
||||
ackFunc AckFunc
|
||||
nackFunc NackFunc
|
||||
}
|
||||
|
||||
// Unmarshal the events message into an object
|
||||
func (e *Event) Unmarshal(v interface{}) error {
|
||||
return json.Unmarshal(e.Payload, v)
|
||||
}
|
||||
|
||||
// Ack acknowledges successful processing of the event in ManualAck mode
|
||||
func (e *Event) Ack() error {
|
||||
return e.ackFunc()
|
||||
}
|
||||
|
||||
func (e *Event) SetAckFunc(f AckFunc) {
|
||||
e.ackFunc = f
|
||||
}
|
||||
|
||||
// Nack negatively acknowledges processing of the event (i.e. failure) in ManualAck mode
|
||||
func (e *Event) Nack() error {
|
||||
return e.nackFunc()
|
||||
}
|
||||
|
||||
func (e *Event) SetNackFunc(f NackFunc) {
|
||||
e.nackFunc = f
|
||||
}
|
||||
|
@@ -1,185 +0,0 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/events"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testPayload struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
stream, err := NewStream()
|
||||
assert.Nilf(t, err, "NewStream should not return an error")
|
||||
assert.NotNilf(t, stream, "NewStream should return a stream object")
|
||||
|
||||
// TestMissingTopic will test the topic validation on publish
|
||||
t.Run("TestMissingTopic", func(t *testing.T) {
|
||||
err := stream.Publish("")
|
||||
assert.Equalf(t, err, events.ErrMissingTopic, "Publishing to a blank topic should return an error")
|
||||
})
|
||||
|
||||
// TestFirehose will publish a message to the test topic. The subscriber will subscribe to the
|
||||
// firehose topic (indicated by a lack of the topic option).
|
||||
t.Run("TestFirehose", func(t *testing.T) {
|
||||
payload := &testPayload{Message: "HelloWorld"}
|
||||
metadata := map[string]string{"foo": "bar"}
|
||||
|
||||
// create the subscriber
|
||||
evChan, err := stream.Subscribe()
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
// setup the subscriber async
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
err = stream.Publish("test",
|
||||
events.WithPayload(payload),
|
||||
events.WithMetadata(metadata),
|
||||
)
|
||||
assert.Nil(t, err, "Publishing a valid message should not return an error")
|
||||
wg.Add(1)
|
||||
|
||||
// wait for the subscriber to recieve the message or timeout
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
// TestSubscribeTopic will publish a message to the test topic. The subscriber will subscribe to the
|
||||
// same test topic.
|
||||
t.Run("TestSubscribeTopic", func(t *testing.T) {
|
||||
payload := &testPayload{Message: "HelloWorld"}
|
||||
metadata := map[string]string{"foo": "bar"}
|
||||
|
||||
// create the subscriber
|
||||
evChan, err := stream.Subscribe(events.WithTopic("test"))
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
// setup the subscriber async
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
err = stream.Publish("test",
|
||||
events.WithPayload(payload),
|
||||
events.WithMetadata(metadata),
|
||||
)
|
||||
assert.Nil(t, err, "Publishing a valid message should not return an error")
|
||||
wg.Add(1)
|
||||
|
||||
// wait for the subscriber to recieve the message or timeout
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
// TestSubscribeQueue will publish a message to a random topic. Two subscribers will then consume
|
||||
// the message from the firehose topic with different queues. The second subscriber will be registered
|
||||
// after the message is published to test durability.
|
||||
t.Run("TestSubscribeQueue", func(t *testing.T) {
|
||||
topic := uuid.New().String()
|
||||
payload := &testPayload{Message: "HelloWorld"}
|
||||
metadata := map[string]string{"foo": "bar"}
|
||||
|
||||
// create the first subscriber
|
||||
evChan1, err := stream.Subscribe(events.WithTopic(topic))
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
// setup the subscriber async
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan1:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
err = stream.Publish(topic,
|
||||
events.WithPayload(payload),
|
||||
events.WithMetadata(metadata),
|
||||
)
|
||||
assert.Nil(t, err, "Publishing a valid message should not return an error")
|
||||
wg.Add(2)
|
||||
|
||||
// create the second subscriber
|
||||
evChan2, err := stream.Subscribe(
|
||||
events.WithTopic(topic),
|
||||
events.WithQueue("second_queue"),
|
||||
events.WithStartAtTime(time.Now().Add(time.Minute*-1)),
|
||||
)
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan2:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for the subscriber to recieve the message or timeout
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
@@ -1,200 +0,0 @@
|
||||
package nats
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/events"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testPayload struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
_, err := exec.LookPath("nats-streaming-server")
|
||||
if err != nil {
|
||||
t.Skipf("Skipping nats test, nats-streaming-server binary is not detected")
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("tcp", ":4222", time.Millisecond*100)
|
||||
if err != nil {
|
||||
t.Skipf("Skipping nats test, could not connect to cluster on port 4222: %v", err)
|
||||
}
|
||||
if err := conn.Close(); err != nil {
|
||||
t.Fatalf("Error closing test tcp connection to nats cluster")
|
||||
}
|
||||
|
||||
stream, err := NewStream(ClusterID("test-cluster"))
|
||||
assert.Nilf(t, err, "NewStream should not return an error")
|
||||
assert.NotNilf(t, stream, "NewStream should return a stream object")
|
||||
|
||||
// TestMissingTopic will test the topic validation on publish
|
||||
t.Run("TestMissingTopic", func(t *testing.T) {
|
||||
err := stream.Publish("")
|
||||
assert.Equalf(t, err, events.ErrMissingTopic, "Publishing to a blank topic should return an error")
|
||||
})
|
||||
|
||||
// TestFirehose will publish a message to the test topic. The subscriber will subscribe to the
|
||||
// firehose topic (indicated by a lack of the topic option).
|
||||
t.Run("TestFirehose", func(t *testing.T) {
|
||||
payload := &testPayload{Message: "HelloWorld"}
|
||||
metadata := map[string]string{"foo": "bar"}
|
||||
|
||||
// create the subscriber
|
||||
evChan, err := stream.Subscribe()
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
// setup the subscriber async
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
err = stream.Publish("test",
|
||||
events.WithPayload(payload),
|
||||
events.WithMetadata(metadata),
|
||||
)
|
||||
assert.Nil(t, err, "Publishing a valid message should not return an error")
|
||||
wg.Add(1)
|
||||
|
||||
// wait for the subscriber to recieve the message or timeout
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
// TestSubscribeTopic will publish a message to the test topic. The subscriber will subscribe to the
|
||||
// same test topic.
|
||||
t.Run("TestSubscribeTopic", func(t *testing.T) {
|
||||
payload := &testPayload{Message: "HelloWorld"}
|
||||
metadata := map[string]string{"foo": "bar"}
|
||||
|
||||
// create the subscriber
|
||||
evChan, err := stream.Subscribe(events.WithTopic("test"))
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
// setup the subscriber async
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
err = stream.Publish("test",
|
||||
events.WithPayload(payload),
|
||||
events.WithMetadata(metadata),
|
||||
)
|
||||
assert.Nil(t, err, "Publishing a valid message should not return an error")
|
||||
wg.Add(1)
|
||||
|
||||
// wait for the subscriber to recieve the message or timeout
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
// TestSubscribeQueue will publish a message to a random topic. Two subscribers will then consume
|
||||
// the message from the firehose topic with different queues. The second subscriber will be registered
|
||||
// after the message is published to test durability.
|
||||
t.Run("TestSubscribeQueue", func(t *testing.T) {
|
||||
topic := uuid.New().String()
|
||||
payload := &testPayload{Message: "HelloWorld"}
|
||||
metadata := map[string]string{"foo": "bar"}
|
||||
|
||||
// create the first subscriber
|
||||
evChan1, err := stream.Subscribe(events.WithTopic(topic))
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
// setup the subscriber async
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan1:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
err = stream.Publish(topic,
|
||||
events.WithPayload(payload),
|
||||
events.WithMetadata(metadata),
|
||||
)
|
||||
assert.Nil(t, err, "Publishing a valid message should not return an error")
|
||||
wg.Add(2)
|
||||
|
||||
// create the second subscriber
|
||||
evChan2, err := stream.Subscribe(
|
||||
events.WithTopic(topic),
|
||||
events.WithQueue("second_queue"),
|
||||
events.WithStartAtTime(time.Now().Add(time.Minute*-1)),
|
||||
)
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan2:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for the subscriber to recieve the message or timeout
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
@@ -6,9 +6,6 @@ import "time"
|
||||
type PublishOptions struct {
|
||||
// Metadata contains any keys which can be used to query the data, for example a customer id
|
||||
Metadata map[string]string
|
||||
// Payload contains any additonal data which is relevent to the event but does not need to be
|
||||
// indexed such as structured data
|
||||
Payload interface{}
|
||||
// Timestamp to set for the event, if the timestamp is a zero value, the current time will be used
|
||||
Timestamp time.Time
|
||||
}
|
||||
@@ -23,13 +20,6 @@ func WithMetadata(md map[string]string) PublishOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPayload sets the payload field on PublishOptions
|
||||
func WithPayload(p interface{}) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.Payload = p
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimestamp sets the timestamp field on PublishOptions
|
||||
func WithTimestamp(t time.Time) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
@@ -42,12 +32,20 @@ type SubscribeOptions struct {
|
||||
// Queue is the name of the subscribers queue, if two subscribers have the same queue the message
|
||||
// should only be published to one of them
|
||||
Queue string
|
||||
// Topic to subscribe to, if left blank the consumer will be subscribed to the firehouse topic which
|
||||
// recieves all events
|
||||
Topic string
|
||||
// StartAtTime is the time from which the messages should be consumed from. If not provided then
|
||||
// the messages will be consumed starting from the moment the Subscription starts.
|
||||
StartAtTime time.Time
|
||||
// AutoAck if true (default true), automatically acknowledges every message so it will not be redelivered.
|
||||
// If false specifies that each message need ts to be manually acknowledged by the subscriber.
|
||||
// If processing is successful the message should be ack'ed to remove the message from the stream.
|
||||
// If processing is unsuccessful the message should be nack'ed (negative acknowledgement) which will mean it will
|
||||
// remain on the stream to be processed again.
|
||||
AutoAck bool
|
||||
AckWait time.Duration
|
||||
// RetryLimit indicates number of times a message is retried
|
||||
RetryLimit int
|
||||
// CustomRetries indicates whether to use RetryLimit
|
||||
CustomRetries bool
|
||||
}
|
||||
|
||||
// SubscribeOption sets attributes on SubscribeOptions
|
||||
@@ -60,13 +58,6 @@ func WithQueue(q string) SubscribeOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithTopic sets the topic to subscribe to
|
||||
func WithTopic(t string) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.Topic = t
|
||||
}
|
||||
}
|
||||
|
||||
// WithStartAtTime sets the StartAtTime field on SubscribeOptions to the value provided
|
||||
func WithStartAtTime(t time.Time) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
@@ -74,6 +65,31 @@ func WithStartAtTime(t time.Time) SubscribeOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithAutoAck sets the AutoAck field on SubscribeOptions and an ackWait duration after which if no ack is received
|
||||
// the message is requeued in case auto ack is turned off
|
||||
func WithAutoAck(ack bool, ackWait time.Duration) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.AutoAck = ack
|
||||
o.AckWait = ackWait
|
||||
}
|
||||
}
|
||||
|
||||
// WithRetryLimit sets the RetryLimit field on SubscribeOptions.
|
||||
// Set to -1 for infinite retries (default)
|
||||
func WithRetryLimit(retries int) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.RetryLimit = retries
|
||||
o.CustomRetries = true
|
||||
}
|
||||
}
|
||||
|
||||
func (s SubscribeOptions) GetRetryLimit() int {
|
||||
if !s.CustomRetries {
|
||||
return -1
|
||||
}
|
||||
return s.RetryLimit
|
||||
}
|
||||
|
||||
// WriteOptions contains all the options which can be provided when writing an event to a store
|
||||
type WriteOptions struct {
|
||||
// TTL is the duration the event should be recorded for, a zero value TTL indicates the event should
|
||||
@@ -93,47 +109,24 @@ func WithTTL(d time.Duration) WriteOption {
|
||||
|
||||
// ReadOptions contains all the options which can be provided when reading events from a store
|
||||
type ReadOptions struct {
|
||||
// Topic to read events from, if no topic is provided events from all topics will be returned
|
||||
Topic string
|
||||
// Query to filter the results using. The store will query the metadata provided when the event
|
||||
// was written to the store
|
||||
Query map[string]string
|
||||
// Limit the number of results to return
|
||||
Limit int
|
||||
Limit uint
|
||||
// Offset the results by this number, useful for paginated queries
|
||||
Offset int
|
||||
Offset uint
|
||||
}
|
||||
|
||||
// ReadOption sets attributes on ReadOptions
|
||||
type ReadOption func(o *ReadOptions)
|
||||
|
||||
// ReadTopic sets the topic attribute on ReadOptions
|
||||
func ReadTopic(t string) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Topic = t
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFilter sets a key and value in the query
|
||||
func ReadFilter(key, value string) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
if o.Query == nil {
|
||||
o.Query = map[string]string{key: value}
|
||||
} else {
|
||||
o.Query[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReadLimit sets the limit attribute on ReadOptions
|
||||
func ReadLimit(l int) ReadOption {
|
||||
func ReadLimit(l uint) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Limit = 1
|
||||
}
|
||||
}
|
||||
|
||||
// ReadOffset sets the offset attribute on ReadOptions
|
||||
func ReadOffset(l int) ReadOption {
|
||||
func ReadOffset(l uint) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Offset = 1
|
||||
}
|
||||
|
28
events/store/options.go
Normal file
28
events/store/options.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/store"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Store store.Store
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
type Option func(o *Options)
|
||||
|
||||
// WithStore sets the underlying store to use
|
||||
func WithStore(s store.Store) Option {
|
||||
return func(o *Options) {
|
||||
o.Store = s
|
||||
}
|
||||
}
|
||||
|
||||
// WithTTL sets the default TTL
|
||||
func WithTTL(ttl time.Duration) Option {
|
||||
return func(o *Options) {
|
||||
o.TTL = ttl
|
||||
}
|
||||
}
|
103
events/store/store.go
Normal file
103
events/store/store.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/events"
|
||||
gostore "github.com/micro/go-micro/v3/store"
|
||||
"github.com/micro/go-micro/v3/store/memory"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const joinKey = "/"
|
||||
|
||||
// NewStore returns an initialized events store
|
||||
func NewStore(opts ...Option) events.Store {
|
||||
// parse the options
|
||||
var options Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.TTL.Seconds() == 0 {
|
||||
options.TTL = time.Hour * 24
|
||||
}
|
||||
if options.Store == nil {
|
||||
options.Store = memory.NewStore()
|
||||
}
|
||||
|
||||
// return the store
|
||||
return &evStore{options}
|
||||
}
|
||||
|
||||
type evStore struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
// Read events for a topic
|
||||
func (s *evStore) Read(topic string, opts ...events.ReadOption) ([]*events.Event, error) {
|
||||
// validate the topic
|
||||
if len(topic) == 0 {
|
||||
return nil, events.ErrMissingTopic
|
||||
}
|
||||
|
||||
// parse the options
|
||||
options := events.ReadOptions{
|
||||
Offset: 0,
|
||||
Limit: 250,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// execute the request
|
||||
recs, err := s.opts.Store.Read(topic+joinKey,
|
||||
gostore.ReadPrefix(),
|
||||
gostore.ReadLimit(options.Limit),
|
||||
gostore.ReadOffset(options.Offset),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading from store")
|
||||
}
|
||||
|
||||
// unmarshal the result
|
||||
result := make([]*events.Event, len(recs))
|
||||
for i, r := range recs {
|
||||
var e events.Event
|
||||
if err := json.Unmarshal(r.Value, &e); err != nil {
|
||||
return nil, errors.Wrap(err, "Invalid event returned from stroe")
|
||||
}
|
||||
result[i] = &e
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Write an event to the store
|
||||
func (s *evStore) Write(event *events.Event, opts ...events.WriteOption) error {
|
||||
// parse the options
|
||||
options := events.WriteOptions{
|
||||
TTL: s.opts.TTL,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// construct the store record
|
||||
bytes, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error mashaling event to JSON")
|
||||
}
|
||||
record := &gostore.Record{
|
||||
Key: event.Topic + joinKey + event.ID,
|
||||
Value: bytes,
|
||||
Expiry: options.TTL,
|
||||
}
|
||||
|
||||
// write the record to the store
|
||||
if err := s.opts.Store.Write(record); err != nil {
|
||||
return errors.Wrap(err, "Error writing to the store")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
48
events/store/store_test.go
Normal file
48
events/store/store_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/events"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
store := NewStore()
|
||||
|
||||
testData := []events.Event{
|
||||
{ID: uuid.New().String(), Topic: "foo"},
|
||||
{ID: uuid.New().String(), Topic: "foo"},
|
||||
{ID: uuid.New().String(), Topic: "bar"},
|
||||
}
|
||||
|
||||
// write the records to the store
|
||||
t.Run("Write", func(t *testing.T) {
|
||||
for _, event := range testData {
|
||||
err := store.Write(&event)
|
||||
assert.Nilf(t, err, "Writing an event should not return an error")
|
||||
}
|
||||
})
|
||||
|
||||
// should not be able to read events from a blank topic
|
||||
t.Run("ReadMissingTopic", func(t *testing.T) {
|
||||
evs, err := store.Read("")
|
||||
assert.Equal(t, err, events.ErrMissingTopic, "Reading a blank topic should return an error")
|
||||
assert.Nil(t, evs, "No events should be returned")
|
||||
})
|
||||
|
||||
// should only get the events from the topic requested
|
||||
t.Run("ReadTopic", func(t *testing.T) {
|
||||
evs, err := store.Read("foo")
|
||||
assert.Nilf(t, err, "No error should be returned")
|
||||
assert.Len(t, evs, 2, "Only the events for this topic should be returned")
|
||||
})
|
||||
|
||||
// limits should be honoured
|
||||
t.Run("ReadTopicLimit", func(t *testing.T) {
|
||||
evs, err := store.Read("foo", events.ReadLimit(1))
|
||||
assert.Nilf(t, err, "No error should be returned")
|
||||
assert.Len(t, evs, 1, "The result should include no more than the read limit")
|
||||
})
|
||||
}
|
@@ -32,6 +32,12 @@ type subscriber struct {
|
||||
Queue string
|
||||
Topic string
|
||||
Channel chan events.Event
|
||||
|
||||
sync.RWMutex
|
||||
retryMap map[string]int
|
||||
retryLimit int
|
||||
autoAck bool
|
||||
ackWait time.Duration
|
||||
}
|
||||
|
||||
type mem struct {
|
||||
@@ -41,7 +47,7 @@ type mem struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (m *mem) Publish(topic string, opts ...events.PublishOption) error {
|
||||
func (m *mem) Publish(topic string, msg interface{}, opts ...events.PublishOption) error {
|
||||
// validate the topic
|
||||
if len(topic) == 0 {
|
||||
return events.ErrMissingTopic
|
||||
@@ -57,10 +63,10 @@ func (m *mem) Publish(topic string, opts ...events.PublishOption) error {
|
||||
|
||||
// encode the message if it's not already encoded
|
||||
var payload []byte
|
||||
if p, ok := options.Payload.([]byte); ok {
|
||||
if p, ok := msg.([]byte); ok {
|
||||
payload = p
|
||||
} else {
|
||||
p, err := json.Marshal(options.Payload)
|
||||
p, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return events.ErrEncodingMessage
|
||||
}
|
||||
@@ -94,20 +100,38 @@ func (m *mem) Publish(topic string, opts ...events.PublishOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mem) Subscribe(opts ...events.SubscribeOption) (<-chan events.Event, error) {
|
||||
func (m *mem) Subscribe(topic string, opts ...events.SubscribeOption) (<-chan events.Event, error) {
|
||||
// validate the topic
|
||||
if len(topic) == 0 {
|
||||
return nil, events.ErrMissingTopic
|
||||
}
|
||||
|
||||
// parse the options
|
||||
options := events.SubscribeOptions{
|
||||
Queue: uuid.New().String(),
|
||||
Queue: uuid.New().String(),
|
||||
AutoAck: true,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
// TODO RetryLimit
|
||||
|
||||
// setup the subscriber
|
||||
sub := &subscriber{
|
||||
Channel: make(chan events.Event),
|
||||
Topic: options.Topic,
|
||||
Queue: options.Queue,
|
||||
Channel: make(chan events.Event),
|
||||
Topic: topic,
|
||||
Queue: options.Queue,
|
||||
retryMap: map[string]int{},
|
||||
autoAck: true,
|
||||
retryLimit: options.GetRetryLimit(),
|
||||
}
|
||||
|
||||
if !options.AutoAck {
|
||||
if options.AckWait == 0 {
|
||||
return nil, fmt.Errorf("invalid AckWait passed, should be positive integer")
|
||||
}
|
||||
sub.autoAck = options.AutoAck
|
||||
sub.ackWait = options.AckWait
|
||||
}
|
||||
|
||||
// register the subscriber
|
||||
@@ -124,16 +148,11 @@ func (m *mem) Subscribe(opts ...events.SubscribeOption) (<-chan events.Event, er
|
||||
return sub.Channel, nil
|
||||
}
|
||||
|
||||
// lookupPreviousEvents finds events for a subscriber which occured before a given time and sends
|
||||
// lookupPreviousEvents finds events for a subscriber which occurred before a given time and sends
|
||||
// them into the subscribers channel
|
||||
func (m *mem) lookupPreviousEvents(sub *subscriber, startTime time.Time) {
|
||||
var prefix string
|
||||
if len(sub.Topic) > 0 {
|
||||
prefix = sub.Topic + "/"
|
||||
}
|
||||
|
||||
// lookup all events which match the topic (a blank topic will return all results)
|
||||
recs, err := m.store.Read(prefix, store.ReadPrefix())
|
||||
recs, err := m.store.Read(sub.Topic+"/", store.ReadPrefix())
|
||||
if err != nil && logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("Error looking up previous events: %v", err)
|
||||
return
|
||||
@@ -150,8 +169,7 @@ func (m *mem) lookupPreviousEvents(sub *subscriber, startTime time.Time) {
|
||||
if ev.Timestamp.Unix() < startTime.Unix() {
|
||||
continue
|
||||
}
|
||||
|
||||
sub.Channel <- ev
|
||||
sendEvent(&ev, sub)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,9 +191,60 @@ func (m *mem) handleEvent(ev *events.Event) {
|
||||
}
|
||||
|
||||
// send the message to each channel async (since one channel might be blocked)
|
||||
for _, sub := range subs {
|
||||
go func(s *subscriber) {
|
||||
s.Channel <- *ev
|
||||
}(sub)
|
||||
for _, sub := range filteredSubs {
|
||||
sendEvent(ev, sub)
|
||||
}
|
||||
}
|
||||
|
||||
func sendEvent(ev *events.Event, sub *subscriber) {
|
||||
go func(s *subscriber) {
|
||||
evCopy := *ev
|
||||
if s.autoAck {
|
||||
s.Channel <- evCopy
|
||||
return
|
||||
}
|
||||
evCopy.SetAckFunc(ackFunc(s, evCopy))
|
||||
evCopy.SetNackFunc(nackFunc(s, evCopy))
|
||||
s.retryMap[evCopy.ID] = 0
|
||||
tick := time.NewTicker(s.ackWait)
|
||||
defer tick.Stop()
|
||||
for range tick.C {
|
||||
s.Lock()
|
||||
count, ok := s.retryMap[evCopy.ID]
|
||||
s.Unlock()
|
||||
if !ok {
|
||||
// success
|
||||
break
|
||||
}
|
||||
|
||||
if s.retryLimit > -1 && count > s.retryLimit {
|
||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("Message retry limit reached, discarding: %v %d %d", evCopy.ID, count, s.retryLimit)
|
||||
}
|
||||
s.Lock()
|
||||
delete(s.retryMap, evCopy.ID)
|
||||
s.Unlock()
|
||||
return
|
||||
}
|
||||
s.Channel <- evCopy
|
||||
s.Lock()
|
||||
s.retryMap[evCopy.ID] = count + 1
|
||||
s.Unlock()
|
||||
}
|
||||
}(sub)
|
||||
}
|
||||
|
||||
func ackFunc(s *subscriber, evCopy events.Event) func() error {
|
||||
return func() error {
|
||||
s.Lock()
|
||||
delete(s.retryMap, evCopy.ID)
|
||||
s.Unlock()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func nackFunc(s *subscriber, evCopy events.Event) func() error {
|
||||
return func() error {
|
||||
return nil
|
||||
}
|
||||
}
|
1
events/stream/memory/memory_test.go
Normal file
1
events/stream/memory/memory_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package memory
|
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
const (
|
||||
defaultClusterID = "micro"
|
||||
eventsTopic = "events"
|
||||
)
|
||||
|
||||
// NewStream returns an initialized nats stream or an error if the connection to the nats
|
||||
@@ -59,7 +58,7 @@ type stream struct {
|
||||
}
|
||||
|
||||
// Publish a message to a topic
|
||||
func (s *stream) Publish(topic string, opts ...events.PublishOption) error {
|
||||
func (s *stream) Publish(topic string, msg interface{}, opts ...events.PublishOption) error {
|
||||
// validate the topic
|
||||
if len(topic) == 0 {
|
||||
return events.ErrMissingTopic
|
||||
@@ -75,10 +74,10 @@ func (s *stream) Publish(topic string, opts ...events.PublishOption) error {
|
||||
|
||||
// encode the message if it's not already encoded
|
||||
var payload []byte
|
||||
if p, ok := options.Payload.([]byte); ok {
|
||||
if p, ok := msg.([]byte); ok {
|
||||
payload = p
|
||||
} else {
|
||||
p, err := json.Marshal(options.Payload)
|
||||
p, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return events.ErrEncodingMessage
|
||||
}
|
||||
@@ -100,11 +99,6 @@ func (s *stream) Publish(topic string, opts ...events.PublishOption) error {
|
||||
return errors.Wrap(err, "Error encoding event")
|
||||
}
|
||||
|
||||
// publish the event to the events channel
|
||||
if _, err := s.conn.PublishAsync(eventsTopic, bytes, nil); err != nil {
|
||||
return errors.Wrap(err, "Error publishing message to events")
|
||||
}
|
||||
|
||||
// publish the event to the topic's channel
|
||||
if _, err := s.conn.PublishAsync(event.Topic, bytes, nil); err != nil {
|
||||
return errors.Wrap(err, "Error publishing message to topic")
|
||||
@@ -114,11 +108,16 @@ func (s *stream) Publish(topic string, opts ...events.PublishOption) error {
|
||||
}
|
||||
|
||||
// Subscribe to a topic
|
||||
func (s *stream) Subscribe(opts ...events.SubscribeOption) (<-chan events.Event, error) {
|
||||
func (s *stream) Subscribe(topic string, opts ...events.SubscribeOption) (<-chan events.Event, error) {
|
||||
// validate the topic
|
||||
if len(topic) == 0 {
|
||||
return nil, events.ErrMissingTopic
|
||||
}
|
||||
|
||||
// parse the options
|
||||
options := events.SubscribeOptions{
|
||||
Topic: eventsTopic,
|
||||
Queue: uuid.New().String(),
|
||||
Queue: uuid.New().String(),
|
||||
AutoAck: true,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@@ -127,19 +126,43 @@ func (s *stream) Subscribe(opts ...events.SubscribeOption) (<-chan events.Event,
|
||||
// setup the subscriber
|
||||
c := make(chan events.Event)
|
||||
handleMsg := func(m *stan.Msg) {
|
||||
// poison message handling
|
||||
if options.GetRetryLimit() > -1 && m.Redelivered && int(m.RedeliveryCount) > options.GetRetryLimit() {
|
||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("Message retry limit reached, discarding: %v", m.Sequence)
|
||||
}
|
||||
m.Ack() // ignoring error
|
||||
return
|
||||
}
|
||||
|
||||
// decode the message
|
||||
var evt events.Event
|
||||
if err := json.Unmarshal(m.Data, &evt); err != nil {
|
||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("Error decoding message: %v", err)
|
||||
}
|
||||
// not ackknowledging the message is the way to indicate an error occured
|
||||
// not acknowledging the message is the way to indicate an error occurred
|
||||
return
|
||||
}
|
||||
|
||||
if !options.AutoAck {
|
||||
// set up the ack funcs
|
||||
evt.SetAckFunc(func() error {
|
||||
return m.Ack()
|
||||
})
|
||||
evt.SetNackFunc(func() error {
|
||||
// noop. not acknowledging the message is the way to indicate an error occurred
|
||||
// we have to wait for the ack wait to kick in before the message is resent
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// push onto the channel and wait for the consumer to take the event off before we acknowledge it.
|
||||
c <- evt
|
||||
|
||||
if !options.AutoAck {
|
||||
return
|
||||
}
|
||||
if err := m.Ack(); err != nil && logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("Error acknowledging message: %v", err)
|
||||
}
|
||||
@@ -147,15 +170,18 @@ func (s *stream) Subscribe(opts ...events.SubscribeOption) (<-chan events.Event,
|
||||
|
||||
// setup the options
|
||||
subOpts := []stan.SubscriptionOption{
|
||||
stan.DurableName(options.Topic),
|
||||
stan.DurableName(topic),
|
||||
stan.SetManualAckMode(),
|
||||
}
|
||||
if options.StartAtTime.Unix() > 0 {
|
||||
stan.StartAtTime(options.StartAtTime)
|
||||
subOpts = append(subOpts, stan.StartAtTime(options.StartAtTime))
|
||||
}
|
||||
if options.AckWait > 0 {
|
||||
subOpts = append(subOpts, stan.AckWait(options.AckWait))
|
||||
}
|
||||
|
||||
// connect the subscriber
|
||||
_, err := s.conn.QueueSubscribe(options.Topic, options.Queue, handleMsg, subOpts...)
|
||||
_, err := s.conn.QueueSubscribe(topic, options.Queue, handleMsg, subOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error subscribing to topic")
|
||||
}
|
3
events/stream/nats/nats_test.go
Normal file
3
events/stream/nats/nats_test.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// +build nats
|
||||
|
||||
package nats
|
253
events/stream/test/stream_test.go
Normal file
253
events/stream/test/stream_test.go
Normal file
@@ -0,0 +1,253 @@
|
||||
// +build nats
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/events"
|
||||
"github.com/micro/go-micro/v3/events/stream/memory"
|
||||
"github.com/micro/go-micro/v3/events/stream/nats"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testPayload struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
str events.Stream
|
||||
name string
|
||||
}
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
tcs := []testCase{}
|
||||
|
||||
// NATS specific setup
|
||||
stream, err := nats.NewStream(nats.ClusterID("test-cluster"))
|
||||
assert.Nilf(t, err, "NewStream should not return an error")
|
||||
assert.NotNilf(t, stream, "NewStream should return a stream object")
|
||||
tcs = append(tcs, testCase{str: stream, name: "nats"})
|
||||
|
||||
stream, err = memory.NewStream()
|
||||
assert.Nilf(t, err, "NewStream should not return an error")
|
||||
assert.NotNilf(t, stream, "NewStream should return a stream object")
|
||||
tcs = append(tcs, testCase{str: stream, name: "memory"})
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runTestStream(t, tc.str)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func runTestStream(t *testing.T, stream events.Stream) {
|
||||
// TestMissingTopic will test the topic validation on publish
|
||||
t.Run("TestMissingTopic", func(t *testing.T) {
|
||||
err := stream.Publish("", nil)
|
||||
assert.Equalf(t, err, events.ErrMissingTopic, "Publishing to a blank topic should return an error")
|
||||
})
|
||||
|
||||
// TestSubscribeTopic will publish a message to the test topic. The subscriber will subscribe to the
|
||||
// same test topic.
|
||||
t.Run("TestSubscribeTopic", func(t *testing.T) {
|
||||
payload := &testPayload{Message: "HelloWorld"}
|
||||
metadata := map[string]string{"foo": "bar"}
|
||||
|
||||
// create the subscriber
|
||||
evChan, err := stream.Subscribe("test")
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
// setup the subscriber async
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
err = stream.Publish("test", payload, events.WithMetadata(metadata))
|
||||
assert.Nil(t, err, "Publishing a valid message should not return an error")
|
||||
wg.Add(1)
|
||||
|
||||
// wait for the subscriber to recieve the message or timeout
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
// TestSubscribeQueue will publish a message to a random topic. Two subscribers will then consume
|
||||
// the message from the firehose topic with different queues. The second subscriber will be registered
|
||||
// after the message is published to test durability.
|
||||
t.Run("TestSubscribeQueue", func(t *testing.T) {
|
||||
topic := uuid.New().String()
|
||||
payload := &testPayload{Message: "HelloWorld"}
|
||||
metadata := map[string]string{"foo": "bar"}
|
||||
|
||||
// create the first subscriber
|
||||
evChan1, err := stream.Subscribe(topic)
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
// setup the subscriber async
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Millisecond * 250)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan1:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
err = stream.Publish(topic, payload, events.WithMetadata(metadata))
|
||||
assert.Nil(t, err, "Publishing a valid message should not return an error")
|
||||
wg.Add(2)
|
||||
|
||||
// create the second subscriber
|
||||
evChan2, err := stream.Subscribe(topic,
|
||||
events.WithQueue("second_queue"),
|
||||
events.WithStartAtTime(time.Now().Add(time.Minute*-1)),
|
||||
)
|
||||
assert.Nilf(t, err, "Subscribe should not return an error")
|
||||
|
||||
go func() {
|
||||
timeout := time.NewTimer(time.Second * 1)
|
||||
|
||||
select {
|
||||
case event, _ := <-evChan2:
|
||||
assert.NotNilf(t, event, "The message was nil")
|
||||
assert.Equal(t, event.Metadata, metadata, "Metadata didn't match")
|
||||
|
||||
var result testPayload
|
||||
err = event.Unmarshal(&result)
|
||||
assert.Nil(t, err, "Error decoding result")
|
||||
assert.Equal(t, result, *payload, "Payload didn't match")
|
||||
|
||||
wg.Done()
|
||||
case <-timeout.C:
|
||||
t.Fatalf("Event was not recieved")
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for the subscriber to recieve the message or timeout
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
t.Run("AckingNacking", func(t *testing.T) {
|
||||
ch, err := stream.Subscribe("foobarAck", events.WithAutoAck(false, 5*time.Second))
|
||||
assert.NoError(t, err, "Unexpected error subscribing")
|
||||
assert.NoError(t, stream.Publish("foobarAck", map[string]string{"foo": "message 1"}))
|
||||
assert.NoError(t, stream.Publish("foobarAck", map[string]string{"foo": "message 2"}))
|
||||
|
||||
ev := <-ch
|
||||
ev.Ack()
|
||||
ev = <-ch
|
||||
nacked := ev.ID
|
||||
ev.Nack()
|
||||
select {
|
||||
case ev = <-ch:
|
||||
assert.Equal(t, ev.ID, nacked, "Nacked message should have been received again")
|
||||
assert.NoError(t, ev.Ack())
|
||||
case <-time.After(7 * time.Second):
|
||||
t.Fatalf("Timed out waiting for message to be put back on queue")
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("Retries", func(t *testing.T) {
|
||||
ch, err := stream.Subscribe("foobarRetries", events.WithAutoAck(false, 5*time.Second), events.WithRetryLimit(1))
|
||||
assert.NoError(t, err, "Unexpected error subscribing")
|
||||
assert.NoError(t, stream.Publish("foobarRetries", map[string]string{"foo": "message 1"}))
|
||||
|
||||
ev := <-ch
|
||||
id := ev.ID
|
||||
ev.Nack()
|
||||
ev = <-ch
|
||||
assert.Equal(t, id, ev.ID, "Nacked message should have been received again")
|
||||
ev.Nack()
|
||||
select {
|
||||
case ev = <-ch:
|
||||
t.Fatalf("Unexpected event received")
|
||||
case <-time.After(7 * time.Second):
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("InfiniteRetries", func(t *testing.T) {
|
||||
ch, err := stream.Subscribe("foobarRetriesInf", events.WithAutoAck(false, 2*time.Second))
|
||||
assert.NoError(t, err, "Unexpected error subscribing")
|
||||
assert.NoError(t, stream.Publish("foobarRetriesInf", map[string]string{"foo": "message 1"}))
|
||||
|
||||
count := 0
|
||||
id := ""
|
||||
for {
|
||||
select {
|
||||
case ev := <-ch:
|
||||
if id != "" {
|
||||
assert.Equal(t, id, ev.ID, "Nacked message should have been received again")
|
||||
}
|
||||
id = ev.ID
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("Unexpected event received")
|
||||
}
|
||||
|
||||
count++
|
||||
if count == 11 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("twoSubs", func(t *testing.T) {
|
||||
ch1, err := stream.Subscribe("foobarTwoSubs1", events.WithAutoAck(false, 5*time.Second))
|
||||
assert.NoError(t, err, "Unexpected error subscribing to topic 1")
|
||||
ch2, err := stream.Subscribe("foobarTwoSubs2", events.WithAutoAck(false, 5*time.Second))
|
||||
assert.NoError(t, err, "Unexpected error subscribing to topic 2")
|
||||
|
||||
assert.NoError(t, stream.Publish("foobarTwoSubs2", map[string]string{"foo": "message 1"}))
|
||||
assert.NoError(t, stream.Publish("foobarTwoSubs1", map[string]string{"foo": "message 1"}))
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
ev := <-ch1
|
||||
assert.Equal(t, "foobarTwoSubs1", ev.Topic, "Received message from unexpected topic")
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
ev := <-ch2
|
||||
assert.Equal(t, "foobarTwoSubs2", ev.Topic, "Received message from unexpected topic")
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
8
go.mod
8
go.mod
@@ -19,7 +19,7 @@ require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/ef-ds/deque v1.0.4-0.20190904040645-54cb57c252a1
|
||||
github.com/evanphx/json-patch/v5 v5.0.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/fsouza/go-dockerclient v1.6.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-acme/lego/v3 v3.4.0
|
||||
@@ -41,14 +41,12 @@ require (
|
||||
github.com/kr/pretty v0.2.0
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lib/pq v1.7.0
|
||||
github.com/lucas-clemente/quic-go v0.14.1
|
||||
github.com/miekg/dns v1.1.27
|
||||
github.com/mitchellh/hashstructure v1.0.0
|
||||
github.com/nats-io/nats-streaming-server v0.18.0 // indirect
|
||||
github.com/nats-io/nats.go v1.10.0
|
||||
github.com/nats-io/stan.go v0.7.0
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/onsi/ginkgo v1.12.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -62,14 +60,14 @@ require (
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
go.uber.org/zap v1.13.0
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c // indirect
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
|
||||
google.golang.org/grpc v1.27.0
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
sigs.k8s.io/yaml v1.1.0 // indirect
|
||||
)
|
||||
|
||||
|
49
go.sum
49
go.sum
@@ -38,8 +38,6 @@ github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:i
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 h1:3ILjVyslFbc4jl1w5TWuvvslFD/nDfR2H8tVaMVLrEY=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -71,8 +69,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
|
||||
@@ -130,6 +126,8 @@ github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSY
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsouza/go-dockerclient v1.6.0 h1:f7j+AX94143JL1H3TiqSMkM4EcLDI0De1qD4GGn3Hig=
|
||||
github.com/fsouza/go-dockerclient v1.6.0/go.mod h1:YWwtNPuL4XTX1SKJQk86cWPmmqwx+4np9qfPbb+znGc=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
@@ -171,7 +169,6 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
||||
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
@@ -276,7 +273,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -290,13 +286,6 @@ github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/lucas-clemente/quic-go v0.14.1 h1:c1aKoBZKOPA+49q96B1wGkibyPP0AxYh45WuAoq+87E=
|
||||
github.com/lucas-clemente/quic-go v0.14.1/go.mod h1:Vn3/Fb0/77b02SGhQk36KzOUmXgVpFfizUfW5WMaqyU=
|
||||
github.com/marten-seemann/chacha20 v0.2.0 h1:f40vqzzx+3GdOmzQoItkLX5WLvHgPgyYqFFIO5Gh4hQ=
|
||||
github.com/marten-seemann/chacha20 v0.2.0/go.mod h1:HSdjFau7GzYRj+ahFNwsO3ouVJr1HFkWoEwNDb4TMtE=
|
||||
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
|
||||
github.com/marten-seemann/qtls v0.4.1 h1:YlT8QP3WCCvvok7MGEZkMldXbyqgr8oFg5/n8Gtbkks=
|
||||
github.com/marten-seemann/qtls v0.4.1/go.mod h1:pxVXcHHw1pNIt8Qo0pwSYQEoZ8yYOOPXTCZLQQunvRc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
@@ -306,7 +295,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
|
||||
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
||||
github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
|
||||
@@ -346,14 +334,8 @@ github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
||||
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
@@ -385,21 +367,18 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
|
||||
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
@@ -408,7 +387,6 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
@@ -417,11 +395,9 @@ github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKc
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
@@ -436,7 +412,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@@ -487,11 +462,11 @@ golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaE
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -525,7 +500,6 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -537,8 +511,8 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -571,10 +545,9 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -610,7 +583,6 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c h1:FodBYPZKH5tAN2O60HlglMwXGAeV/4k+NKbli79M/2c=
|
||||
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
@@ -631,7 +603,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@@ -639,7 +610,6 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
@@ -650,7 +620,6 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
@@ -671,7 +640,6 @@ gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
|
||||
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
@@ -680,11 +648,10 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
53
metrics/logging/reporter.go
Normal file
53
metrics/logging/reporter.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultLoggingLevel = logger.TraceLevel
|
||||
)
|
||||
|
||||
// Reporter is an implementation of metrics.Reporter:
|
||||
type Reporter struct {
|
||||
options metrics.Options
|
||||
}
|
||||
|
||||
// New returns a configured logging reporter:
|
||||
func New(opts ...metrics.Option) *Reporter {
|
||||
logger.Logf(logger.InfoLevel, "Metrics/Logging - metrics will be logged (at %s level)", defaultLoggingLevel.String())
|
||||
|
||||
return &Reporter{
|
||||
options: metrics.NewOptions(opts...),
|
||||
}
|
||||
}
|
||||
|
||||
// Count implements the metrics.Reporter interface Count method:
|
||||
func (r *Reporter) Count(metricName string, value int64, tags metrics.Tags) error {
|
||||
logger.Logf(defaultLoggingLevel, "Count metric: (%s: %d) %s", metricName, value, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gauge implements the metrics.Reporter interface Gauge method:
|
||||
func (r *Reporter) Gauge(metricName string, value float64, tags metrics.Tags) error {
|
||||
logger.Logf(defaultLoggingLevel, "Gauge metric: (%s: %f) %s", metricName, value, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timing implements the metrics.Reporter interface Timing method:
|
||||
func (r *Reporter) Timing(metricName string, value time.Duration, tags metrics.Tags) error {
|
||||
logger.Logf(defaultLoggingLevel, "Timing metric: (%s: %s) %s", metricName, value.String(), tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertTags turns Tags into prometheus labels:
|
||||
func convertTags(tags metrics.Tags) map[string]interface{} {
|
||||
labels := make(map[string]interface{})
|
||||
for key, value := range tags {
|
||||
labels[key] = value
|
||||
}
|
||||
return labels
|
||||
}
|
42
metrics/logging/reporter_test.go
Normal file
42
metrics/logging/reporter_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/metrics"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoggingReporter(t *testing.T) {
|
||||
|
||||
// Make a Reporter:
|
||||
reporter := New(metrics.Path("/prometheus"), metrics.DefaultTags(map[string]string{"service": "prometheus-test"}))
|
||||
assert.NotNil(t, reporter)
|
||||
assert.Equal(t, "prometheus-test", reporter.options.DefaultTags["service"])
|
||||
assert.Equal(t, ":9000", reporter.options.Address)
|
||||
assert.Equal(t, "/prometheus", reporter.options.Path)
|
||||
|
||||
// Check that our implementation is valid:
|
||||
assert.Implements(t, new(metrics.Reporter), reporter)
|
||||
|
||||
// Test tag conversion:
|
||||
tags := metrics.Tags{
|
||||
"tag1": "false",
|
||||
"tag2": "true",
|
||||
}
|
||||
convertedTags := convertTags(tags)
|
||||
assert.Equal(t, "false", convertedTags["tag1"])
|
||||
assert.Equal(t, "true", convertedTags["tag2"])
|
||||
|
||||
// Test submitting metrics through the interface methods:
|
||||
assert.NoError(t, reporter.Count("test.counter.1", 6, tags))
|
||||
assert.NoError(t, reporter.Count("test.counter.2", 19, tags))
|
||||
assert.NoError(t, reporter.Count("test.counter.1", 5, tags))
|
||||
assert.NoError(t, reporter.Gauge("test.gauge.1", 99, tags))
|
||||
assert.NoError(t, reporter.Gauge("test.gauge.2", 55, tags))
|
||||
assert.NoError(t, reporter.Gauge("test.gauge.1", 98, tags))
|
||||
assert.NoError(t, reporter.Timing("test.timing.1", time.Second, tags))
|
||||
assert.NoError(t, reporter.Timing("test.timing.2", time.Minute, tags))
|
||||
}
|
@@ -3,7 +3,6 @@ package noop
|
||||
import (
|
||||
"time"
|
||||
|
||||
log "github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/metrics"
|
||||
)
|
||||
|
||||
@@ -14,8 +13,6 @@ type Reporter struct {
|
||||
|
||||
// New returns a configured noop reporter:
|
||||
func New(opts ...metrics.Option) *Reporter {
|
||||
log.Info("Metrics/NoOp - not doing anything")
|
||||
|
||||
return &Reporter{
|
||||
options: metrics.NewOptions(opts...),
|
||||
}
|
||||
|
@@ -5,8 +5,8 @@ var (
|
||||
defaultPrometheusListenAddress = ":9000"
|
||||
// This is the endpoint where the Prometheus metrics will be made available ("/metrics" is the default with Prometheus):
|
||||
defaultPath = "/metrics"
|
||||
// timingObjectives is the default spread of stats we maintain for timings / histograms:
|
||||
defaultTimingObjectives = map[float64]float64{0.0: 0, 0.5: 0.05, 0.75: 0.04, 0.90: 0.03, 0.95: 0.02, 0.98: 0.001, 1: 0}
|
||||
// defaultPercentiles is the default spread of percentiles/quantiles we maintain for timings / histogram metrics:
|
||||
defaultPercentiles = []float64{0, 0.5, 0.75, 0.90, 0.95, 0.98, 0.99, 1}
|
||||
)
|
||||
|
||||
// Option powers the configuration for metrics implementations:
|
||||
@@ -14,19 +14,19 @@ type Option func(*Options)
|
||||
|
||||
// Options for metrics implementations:
|
||||
type Options struct {
|
||||
Address string
|
||||
Path string
|
||||
DefaultTags Tags
|
||||
TimingObjectives map[float64]float64
|
||||
Address string
|
||||
DefaultTags Tags
|
||||
Path string
|
||||
Percentiles []float64
|
||||
}
|
||||
|
||||
// NewOptions prepares a set of options:
|
||||
func NewOptions(opt ...Option) Options {
|
||||
opts := Options{
|
||||
Address: defaultPrometheusListenAddress,
|
||||
DefaultTags: make(Tags),
|
||||
Path: defaultPath,
|
||||
TimingObjectives: defaultTimingObjectives,
|
||||
Address: defaultPrometheusListenAddress,
|
||||
DefaultTags: make(Tags),
|
||||
Path: defaultPath,
|
||||
Percentiles: defaultPercentiles,
|
||||
}
|
||||
|
||||
for _, o := range opt {
|
||||
@@ -57,9 +57,9 @@ func DefaultTags(value Tags) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// TimingObjectives defines the desired spread of statistics for histogram / timing metrics:
|
||||
func TimingObjectives(value map[float64]float64) Option {
|
||||
// Percentiles defines the desired spread of statistics for histogram / timing metrics:
|
||||
func Percentiles(value []float64) Option {
|
||||
return func(o *Options) {
|
||||
o.TimingObjectives = value
|
||||
o.Percentiles = value
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,16 @@ import (
|
||||
func TestOptions(t *testing.T) {
|
||||
|
||||
// Make some new options:
|
||||
options := NewOptions(Path("/prometheus"), DefaultTags(map[string]string{"service": "prometheus-test"}))
|
||||
options := NewOptions(
|
||||
Address(":9999"),
|
||||
DefaultTags(map[string]string{"service": "prometheus-test"}),
|
||||
Path("/prometheus"),
|
||||
Percentiles([]float64{0.11, 0.22, 0.33}),
|
||||
)
|
||||
|
||||
// Check that the defaults and overrides were accepted:
|
||||
assert.Equal(t, ":9000", options.Address)
|
||||
assert.Equal(t, "/prometheus", options.Path)
|
||||
assert.Equal(t, ":9999", options.Address)
|
||||
assert.Equal(t, "prometheus-test", options.DefaultTags["service"])
|
||||
assert.Equal(t, "/prometheus", options.Path)
|
||||
assert.Equal(t, []float64{0.11, 0.22, 0.33}, options.Percentiles)
|
||||
}
|
||||
|
@@ -19,13 +19,22 @@ type metricFamily struct {
|
||||
|
||||
// newMetricFamily returns a new metricFamily (useful in case we want to change the structure later):
|
||||
func (r *Reporter) newMetricFamily() metricFamily {
|
||||
|
||||
// Take quantile thresholds from our pre-defined list:
|
||||
timingObjectives := make(map[float64]float64)
|
||||
for _, percentile := range r.options.Percentiles {
|
||||
if quantileThreshold, ok := quantileThresholds[percentile]; ok {
|
||||
timingObjectives[percentile] = quantileThreshold
|
||||
}
|
||||
}
|
||||
|
||||
return metricFamily{
|
||||
counters: make(map[string]*prometheus.CounterVec),
|
||||
gauges: make(map[string]*prometheus.GaugeVec),
|
||||
timings: make(map[string]*prometheus.SummaryVec),
|
||||
defaultLabels: r.convertTags(r.options.DefaultTags),
|
||||
prometheusRegistry: r.prometheusRegistry,
|
||||
timingObjectives: r.options.TimingObjectives,
|
||||
timingObjectives: timingObjectives,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,12 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
var (
|
||||
// quantileThresholds maps quantiles / percentiles to error thresholds (required by the Prometheus client).
|
||||
// Must be from our pre-defined set [0.0, 0.5, 0.75, 0.90, 0.95, 0.98, 0.99, 1]:
|
||||
quantileThresholds = map[float64]float64{0.0: 0, 0.5: 0.05, 0.75: 0.04, 0.90: 0.03, 0.95: 0.02, 0.98: 0.001, 1: 0}
|
||||
)
|
||||
|
||||
// Reporter is an implementation of metrics.Reporter:
|
||||
type Reporter struct {
|
||||
options metrics.Options
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -12,11 +14,11 @@ import (
|
||||
func TestPrometheusReporter(t *testing.T) {
|
||||
|
||||
// Make a Reporter:
|
||||
reporter, err := New(metrics.Path("/prometheus"), metrics.DefaultTags(map[string]string{"service": "prometheus-test"}))
|
||||
reporter, err := New(metrics.Address(":9999"), metrics.Path("/prometheus"), metrics.DefaultTags(map[string]string{"service": "prometheus-test"}))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, reporter)
|
||||
assert.Equal(t, "prometheus-test", reporter.options.DefaultTags["service"])
|
||||
assert.Equal(t, ":9000", reporter.options.Address)
|
||||
assert.Equal(t, ":9999", reporter.options.Address)
|
||||
assert.Equal(t, "/prometheus", reporter.options.Path)
|
||||
|
||||
// Check that our implementation is valid:
|
||||
@@ -69,5 +71,19 @@ func TestPrometheusReporter(t *testing.T) {
|
||||
assert.Len(t, reporter.metrics.timings, 2)
|
||||
|
||||
// Test reading back the metrics:
|
||||
// This could be done by hitting the /metrics endpoint
|
||||
rsp, err := http.Get("http://localhost:9999/prometheus")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, rsp.StatusCode)
|
||||
|
||||
// Read the response body and check for our metric:
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check for appropriately aggregated metrics:
|
||||
assert.Contains(t, string(bodyBytes), `test_counter_1{service="prometheus-test",tag1="false",tag2="true"} 11`)
|
||||
assert.Contains(t, string(bodyBytes), `test_counter_2{service="prometheus-test",tag1="false",tag2="true"} 19`)
|
||||
assert.Contains(t, string(bodyBytes), `test_gauge_1{service="prometheus-test",tag1="false",tag2="true"} 98`)
|
||||
assert.Contains(t, string(bodyBytes), `test_gauge_2{service="prometheus-test",tag1="false",tag2="true"} 55`)
|
||||
assert.Contains(t, string(bodyBytes), `test_timing_1{service="prometheus-test",tag1="false",tag2="true",quantile="0"} 1`)
|
||||
assert.Contains(t, string(bodyBytes), `test_timing_2{service="prometheus-test",tag1="false",tag2="true",quantile="0"} 60`)
|
||||
}
|
||||
|
@@ -34,8 +34,6 @@ type Entity interface {
|
||||
Value() interface{}
|
||||
// Attributes of the entity
|
||||
Attributes() map[string]interface{}
|
||||
// Read a value as a concrete type
|
||||
Read(v interface{}) error
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
|
41
model/sql/entity.go
Normal file
41
model/sql/entity.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/codec"
|
||||
"github.com/micro/go-micro/v3/model"
|
||||
)
|
||||
|
||||
type sqlEntity struct {
|
||||
id string
|
||||
name string
|
||||
value interface{}
|
||||
codec codec.Marshaler
|
||||
attributes map[string]interface{}
|
||||
}
|
||||
|
||||
func (m *sqlEntity) Attributes() map[string]interface{} {
|
||||
return m.attributes
|
||||
}
|
||||
|
||||
func (m *sqlEntity) Id() string {
|
||||
return m.id
|
||||
}
|
||||
|
||||
func (m *sqlEntity) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
func (m *sqlEntity) Value() interface{} {
|
||||
return m.value
|
||||
}
|
||||
|
||||
func newEntity(name string, value interface{}, codec codec.Marshaler) model.Entity {
|
||||
return &sqlEntity{
|
||||
id: uuid.New().String(),
|
||||
name: name,
|
||||
value: value,
|
||||
codec: codec,
|
||||
attributes: make(map[string]interface{}),
|
||||
}
|
||||
}
|
104
model/sql/sql.go
Normal file
104
model/sql/sql.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Package sql is the micro data model implementation
|
||||
package sql
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/codec/json"
|
||||
"github.com/micro/go-micro/v3/model"
|
||||
"github.com/micro/go-micro/v3/store"
|
||||
"github.com/micro/go-micro/v3/store/memory"
|
||||
memsync "github.com/micro/go-micro/v3/sync/memory"
|
||||
)
|
||||
|
||||
type sqlModel struct {
|
||||
options model.Options
|
||||
}
|
||||
|
||||
func (m *sqlModel) Init(opts ...model.Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *sqlModel) NewEntity(name string, value interface{}) model.Entity {
|
||||
// TODO: potentially pluralise name for tables
|
||||
return newEntity(name, value, m.options.Codec)
|
||||
}
|
||||
|
||||
func (m *sqlModel) Create(e model.Entity) error {
|
||||
// lock on the name of entity
|
||||
if err := m.options.Sync.Lock(e.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: deal with the error
|
||||
defer m.options.Sync.Unlock(e.Name())
|
||||
|
||||
// TODO: potentially add encode to entity?
|
||||
v, err := m.options.Codec.Marshal(e.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: include metadata and set database
|
||||
return m.options.Store.Write(&store.Record{
|
||||
Key: e.Id(),
|
||||
Value: v,
|
||||
}, store.WriteTo(m.options.Database, e.Name()))
|
||||
}
|
||||
|
||||
func (m *sqlModel) Read(opts ...model.ReadOption) ([]model.Entity, error) {
|
||||
var options model.ReadOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
// TODO: implement the options that allow querying
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *sqlModel) Update(e model.Entity) error {
|
||||
// TODO: read out the record first, update the fields and store
|
||||
|
||||
// lock on the name of entity
|
||||
if err := m.options.Sync.Lock(e.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: deal with the error
|
||||
defer m.options.Sync.Unlock(e.Name())
|
||||
|
||||
// TODO: potentially add encode to entity?
|
||||
v, err := m.options.Codec.Marshal(e.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: include metadata and set database
|
||||
return m.options.Store.Write(&store.Record{
|
||||
Key: e.Id(),
|
||||
Value: v,
|
||||
}, store.WriteTo(m.options.Database, e.Name()))
|
||||
}
|
||||
|
||||
func (m *sqlModel) Delete(opts ...model.DeleteOption) error {
|
||||
var options model.DeleteOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
// TODO: implement the options that allow deleting
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *sqlModel) String() string {
|
||||
return "sql"
|
||||
}
|
||||
|
||||
func NewModel(opts ...model.Option) model.Model {
|
||||
options := model.Options{
|
||||
Codec: new(json.Marshaler),
|
||||
Sync: memsync.NewSync(),
|
||||
Store: memory.NewStore(),
|
||||
}
|
||||
|
||||
return &sqlModel{
|
||||
options: options,
|
||||
}
|
||||
}
|
@@ -16,16 +16,16 @@ import (
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/network"
|
||||
pb "github.com/micro/go-micro/v3/network/mucp/proto"
|
||||
"github.com/micro/go-micro/v3/network/resolver/dns"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
bun "github.com/micro/go-micro/v3/network/tunnel/broker"
|
||||
tun "github.com/micro/go-micro/v3/network/tunnel/transport"
|
||||
"github.com/micro/go-micro/v3/proxy"
|
||||
"github.com/micro/go-micro/v3/registry/noop"
|
||||
"github.com/micro/go-micro/v3/resolver/dns"
|
||||
"github.com/micro/go-micro/v3/router"
|
||||
"github.com/micro/go-micro/v3/server"
|
||||
smucp "github.com/micro/go-micro/v3/server/mucp"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
bun "github.com/micro/go-micro/v3/tunnel/broker"
|
||||
tun "github.com/micro/go-micro/v3/tunnel/transport"
|
||||
"github.com/micro/go-micro/v3/util/backoff"
|
||||
)
|
||||
|
||||
@@ -961,12 +961,11 @@ func (n *mucpNetwork) processNetChan(listener tunnel.Listener) {
|
||||
route.Metric = d
|
||||
}
|
||||
|
||||
q := []router.QueryOption{
|
||||
router.QueryService(route.Service),
|
||||
router.QueryLink(route.Link),
|
||||
q := []router.LookupOption{
|
||||
router.LookupLink(route.Link),
|
||||
}
|
||||
|
||||
routes, err := n.router.Table().Query(q...)
|
||||
routes, err := n.router.Lookup(route.Service, q...)
|
||||
if err != nil && err != router.ErrRouteNotFound {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Network node %s failed listing best routes for %s: %v", n.id, route.Service, err)
|
||||
@@ -1079,16 +1078,15 @@ func (n *mucpNetwork) processNetChan(listener tunnel.Listener) {
|
||||
}
|
||||
|
||||
// pruneRoutes prunes routes return by given query
|
||||
func (n *mucpNetwork) pruneRoutes(q ...router.QueryOption) error {
|
||||
routes, err := n.router.Table().Query(q...)
|
||||
func (n *mucpNetwork) pruneRoutes(q ...router.LookupOption) error {
|
||||
routes, err := n.router.Table().Read()
|
||||
if err != nil && err != router.ErrRouteNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if err := n.router.Table().Delete(route); err != nil && err != router.ErrRouteNotFound {
|
||||
return err
|
||||
}
|
||||
// filter and delete the routes in question
|
||||
for _, route := range router.Filter(routes, router.NewLookup(q...)) {
|
||||
n.router.Table().Delete(route)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1097,18 +1095,18 @@ func (n *mucpNetwork) pruneRoutes(q ...router.QueryOption) error {
|
||||
// pruneNodeRoutes prunes routes that were either originated by or routable via given node
|
||||
func (n *mucpNetwork) prunePeerRoutes(peer *node) error {
|
||||
// lookup all routes originated by router
|
||||
q := []router.QueryOption{
|
||||
router.QueryRouter(peer.id),
|
||||
router.QueryLink("*"),
|
||||
q := []router.LookupOption{
|
||||
router.LookupRouter(peer.id),
|
||||
router.LookupLink("*"),
|
||||
}
|
||||
if err := n.pruneRoutes(q...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// lookup all routes routable via gw
|
||||
q = []router.QueryOption{
|
||||
router.QueryGateway(peer.address),
|
||||
router.QueryLink("*"),
|
||||
q = []router.LookupOption{
|
||||
router.LookupGateway(peer.address),
|
||||
router.LookupLink("*"),
|
||||
}
|
||||
if err := n.pruneRoutes(q...); err != nil {
|
||||
return err
|
||||
@@ -1260,7 +1258,7 @@ func (n *mucpNetwork) manage() {
|
||||
}
|
||||
|
||||
// get a list of all routes
|
||||
routes, err := n.options.Router.Table().List()
|
||||
routes, err := n.options.Router.Table().Read()
|
||||
if err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Network failed listing routes when pruning peers: %v", err)
|
||||
@@ -1291,7 +1289,7 @@ func (n *mucpNetwork) manage() {
|
||||
}
|
||||
|
||||
// otherwise delete all the routes originated by it
|
||||
if err := n.pruneRoutes(router.QueryRouter(route.Router)); err != nil {
|
||||
if err := n.pruneRoutes(router.LookupRouter(route.Router)); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Network failed deleting routes by %s: %v", route.Router, err)
|
||||
}
|
||||
@@ -1346,7 +1344,7 @@ func (n *mucpNetwork) manage() {
|
||||
// based on the advertisement strategy encoded in protobuf
|
||||
// It returns error if the routes failed to be retrieved from the routing table
|
||||
func (n *mucpNetwork) getProtoRoutes() ([]*pb.Route, error) {
|
||||
routes, err := n.router.Table().List()
|
||||
routes, err := n.router.Table().Read()
|
||||
if err != nil && err != router.ErrRouteNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -2,12 +2,12 @@ package network
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
tmucp "github.com/micro/go-micro/v3/network/tunnel/mucp"
|
||||
"github.com/micro/go-micro/v3/proxy"
|
||||
"github.com/micro/go-micro/v3/proxy/mucp"
|
||||
"github.com/micro/go-micro/v3/router"
|
||||
regRouter "github.com/micro/go-micro/v3/router/registry"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
tmucp "github.com/micro/go-micro/v3/tunnel/mucp"
|
||||
)
|
||||
|
||||
type Option func(*Options)
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/micro/go-micro/v3/resolver"
|
||||
"github.com/micro/go-micro/v3/network/resolver"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/micro/go-micro/v3/resolver"
|
||||
"github.com/micro/go-micro/v3/network/resolver"
|
||||
)
|
||||
|
||||
// Resolver is a DNS network resolve
|
@@ -8,7 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/micro/go-micro/v3/resolver"
|
||||
"github.com/micro/go-micro/v3/network/resolver"
|
||||
)
|
||||
|
||||
// Resolver is a HTTP network resolver
|
@@ -2,7 +2,7 @@
|
||||
package noop
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/resolver"
|
||||
"github.com/micro/go-micro/v3/network/resolver"
|
||||
)
|
||||
|
||||
type Resolver struct{}
|
@@ -2,7 +2,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/resolver"
|
||||
"github.com/micro/go-micro/v3/network/resolver"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/registry/mdns"
|
||||
)
|
@@ -2,7 +2,7 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/resolver"
|
||||
"github.com/micro/go-micro/v3/network/resolver"
|
||||
)
|
||||
|
||||
// Resolver returns a static list of nodes. In the event the node list
|
@@ -6,7 +6,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
maddr "github.com/micro/go-micro/v3/util/addr"
|
||||
mnet "github.com/micro/go-micro/v3/util/net"
|
||||
mls "github.com/micro/go-micro/v3/util/tls"
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
pb "github.com/micro/go-micro/v3/transport/grpc/proto"
|
||||
pb "github.com/micro/go-micro/v3/network/transport/grpc/proto"
|
||||
)
|
||||
|
||||
type grpcTransport struct {
|
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
func expectedPort(t *testing.T, expected string, lsn transport.Listener) {
|
@@ -5,8 +5,8 @@ import (
|
||||
|
||||
"github.com/micro/go-micro/v3/errors"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
pb "github.com/micro/go-micro/v3/transport/grpc/proto"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
pb "github.com/micro/go-micro/v3/network/transport/grpc/proto"
|
||||
"google.golang.org/grpc/peer"
|
||||
)
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
pb "github.com/micro/go-micro/v3/transport/grpc/proto"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
pb "github.com/micro/go-micro/v3/network/transport/grpc/proto"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
@@ -13,7 +13,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
maddr "github.com/micro/go-micro/v3/util/addr"
|
||||
"github.com/micro/go-micro/v3/util/buf"
|
||||
mnet "github.com/micro/go-micro/v3/util/net"
|
@@ -4,7 +4,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
func call(b *testing.B, c int) {
|
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
func expectedPort(t *testing.T, expected string, lsn transport.Listener) {
|
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
// Handle registers the handler for the given pattern.
|
@@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
maddr "github.com/micro/go-micro/v3/util/addr"
|
||||
mnet "github.com/micro/go-micro/v3/util/net"
|
||||
)
|
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
func TestMemoryTransport(t *testing.T) {
|
@@ -5,9 +5,9 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/v3/broker"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
"github.com/micro/go-micro/v3/tunnel/mucp"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
"github.com/micro/go-micro/v3/network/tunnel/mucp"
|
||||
)
|
||||
|
||||
type tunBroker struct {
|
@@ -6,7 +6,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
"github.com/oxtoacart/bpool"
|
||||
)
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
type link struct {
|
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
)
|
||||
|
||||
type tunListener struct {
|
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
)
|
||||
|
||||
var (
|
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
)
|
||||
|
||||
func testBrokenTunAccept(t *testing.T, tun tunnel.Tunnel, wait chan bool, wg *sync.WaitGroup) {
|
@@ -8,8 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
)
|
||||
|
||||
// session is our pseudo session for transport.Socket
|
@@ -4,8 +4,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/transport/quic"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -140,6 +140,6 @@ func DefaultOptions() Options {
|
||||
Id: uuid.New().String(),
|
||||
Address: DefaultAddress,
|
||||
Token: DefaultToken,
|
||||
Transport: quic.NewTransport(),
|
||||
Transport: grpc.NewTransport(),
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
)
|
||||
|
||||
type tunListener struct {
|
@@ -4,9 +4,9 @@ package transport
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/tunnel"
|
||||
"github.com/micro/go-micro/v3/tunnel/mucp"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
"github.com/micro/go-micro/v3/network/tunnel"
|
||||
"github.com/micro/go-micro/v3/network/tunnel/mucp"
|
||||
)
|
||||
|
||||
type tunTransport struct {
|
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/transport"
|
||||
"github.com/micro/go-micro/v3/network/transport"
|
||||
)
|
||||
|
||||
const (
|
@@ -94,10 +94,7 @@ func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server
|
||||
logger.Tracef("Proxy received request for %s %s", service, endpoint)
|
||||
}
|
||||
|
||||
// no retries with the proxy
|
||||
opts := []client.CallOption{
|
||||
client.WithRetries(0),
|
||||
}
|
||||
var opts []client.CallOption
|
||||
|
||||
// call a specific backend
|
||||
if len(p.Endpoint) > 0 {
|
||||
|
@@ -208,7 +208,7 @@ func (p *Proxy) getRoute(ctx context.Context, service string) ([]router.Route, e
|
||||
|
||||
func (p *Proxy) cacheRoutes(service string) ([]router.Route, error) {
|
||||
// lookup the routes in the router
|
||||
results, err := p.Router.Lookup(router.QueryService(service), router.QueryNetwork("*"))
|
||||
results, err := p.Router.Lookup(service, router.LookupNetwork("*"))
|
||||
if err != nil {
|
||||
// assumption that we're ok with stale routes
|
||||
logger.Debugf("Failed to lookup route for %s: %v", service, err)
|
||||
|
@@ -17,19 +17,17 @@ func NewRouter(opts ...router.Option) router.Router {
|
||||
if len(options.Network) == 0 {
|
||||
options.Network = "micro"
|
||||
}
|
||||
return &dns{options, &table{options}}
|
||||
return &dns{options}
|
||||
}
|
||||
|
||||
type dns struct {
|
||||
options router.Options
|
||||
table *table
|
||||
}
|
||||
|
||||
func (d *dns) Init(opts ...router.Option) error {
|
||||
for _, o := range opts {
|
||||
o(&d.options)
|
||||
}
|
||||
d.table.options = d.options
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -38,50 +36,16 @@ func (d *dns) Options() router.Options {
|
||||
}
|
||||
|
||||
func (d *dns) Table() router.Table {
|
||||
return d.table
|
||||
}
|
||||
|
||||
func (d *dns) Lookup(opts ...router.QueryOption) ([]router.Route, error) {
|
||||
return d.table.Query(opts...)
|
||||
}
|
||||
|
||||
func (d *dns) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dns) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dns) String() string {
|
||||
return "dns"
|
||||
}
|
||||
|
||||
type table struct {
|
||||
options router.Options
|
||||
}
|
||||
|
||||
func (t *table) Create(router.Route) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) Delete(router.Route) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) Update(router.Route) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) List() ([]router.Route, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *table) Query(opts ...router.QueryOption) ([]router.Route, error) {
|
||||
options := router.NewQuery(opts...)
|
||||
|
||||
func (d *dns) Lookup(service string, opts ...router.LookupOption) ([]router.Route, error) {
|
||||
// check to see if we have the port provided in the service, e.g. go-micro-srv-foo:8000
|
||||
host, port, err := net.SplitHostPort(options.Service)
|
||||
host, port, err := net.SplitHostPort(service)
|
||||
if err == nil {
|
||||
// lookup the service using A records
|
||||
ips, err := net.LookupHost(host)
|
||||
@@ -95,7 +59,7 @@ func (t *table) Query(opts ...router.QueryOption) ([]router.Route, error) {
|
||||
result := make([]router.Route, len(ips))
|
||||
for i, ip := range ips {
|
||||
result[i] = router.Route{
|
||||
Service: options.Service,
|
||||
Service: service,
|
||||
Address: fmt.Sprintf("%s:%d", ip, uint16(p)),
|
||||
}
|
||||
}
|
||||
@@ -104,7 +68,7 @@ func (t *table) Query(opts ...router.QueryOption) ([]router.Route, error) {
|
||||
|
||||
// we didn't get the port so we'll lookup the service using SRV records. If we can't lookup the
|
||||
// service using the SRV record, we return the error.
|
||||
_, nodes, err := net.LookupSRV(options.Service, "tcp", t.options.Network)
|
||||
_, nodes, err := net.LookupSRV(service, "tcp", d.options.Network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -113,10 +77,18 @@ func (t *table) Query(opts ...router.QueryOption) ([]router.Route, error) {
|
||||
result := make([]router.Route, len(nodes))
|
||||
for i, n := range nodes {
|
||||
result[i] = router.Route{
|
||||
Service: options.Service,
|
||||
Service: service,
|
||||
Address: fmt.Sprintf("%s:%d", n.Target, n.Port),
|
||||
Network: t.options.Network,
|
||||
Network: d.options.Network,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *dns) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *dns) String() string {
|
||||
return "dns"
|
||||
}
|
||||
|
@@ -42,19 +42,19 @@ func (m *mdnsRouter) Table() router.Table {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mdnsRouter) Lookup(opts ...router.QueryOption) ([]router.Route, error) {
|
||||
options := router.NewQuery(opts...)
|
||||
func (m *mdnsRouter) Lookup(service string, opts ...router.LookupOption) ([]router.Route, error) {
|
||||
options := router.NewLookup(opts...)
|
||||
|
||||
// check to see if we have the port provided in the service, e.g. go-micro-srv-foo:8000
|
||||
service, port, err := net.SplitHostPort(options.Service)
|
||||
srv, port, err := net.SplitHostPort(service)
|
||||
if err != nil {
|
||||
service = options.Service
|
||||
srv = service
|
||||
}
|
||||
|
||||
// query for the host
|
||||
entries := make(chan *mdns.ServiceEntry)
|
||||
|
||||
p := mdns.DefaultParams(service)
|
||||
p := mdns.DefaultParams(srv)
|
||||
p.Timeout = time.Millisecond * 100
|
||||
p.Entries = entries
|
||||
|
||||
|
@@ -22,8 +22,8 @@ type Options struct {
|
||||
Registry registry.Registry
|
||||
// Context for additional options
|
||||
Context context.Context
|
||||
// Precache routes
|
||||
Precache bool
|
||||
// Cache routes
|
||||
Cache bool
|
||||
}
|
||||
|
||||
// Id sets Router Id
|
||||
@@ -61,10 +61,10 @@ func Registry(r registry.Registry) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Precache the routes
|
||||
func Precache() Option {
|
||||
// Cache the routes
|
||||
func Cache() Option {
|
||||
return func(o *Options) {
|
||||
o.Precache = true
|
||||
o.Cache = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,3 +77,16 @@ func DefaultOptions() Options {
|
||||
Context: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
type ReadOptions struct {
|
||||
Service string
|
||||
}
|
||||
|
||||
type ReadOption func(o *ReadOptions)
|
||||
|
||||
// ReadService sets the service to read from the table
|
||||
func ReadService(s string) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Service = s
|
||||
}
|
||||
}
|
||||
|
117
router/query.go
117
router/query.go
@@ -1,13 +1,11 @@
|
||||
package router
|
||||
|
||||
// QueryOption sets routing table query options
|
||||
type QueryOption func(*QueryOptions)
|
||||
// LookupOption sets routing table query options
|
||||
type LookupOption func(*LookupOptions)
|
||||
|
||||
// QueryOptions are routing table query options
|
||||
// LookupOptions are routing table query options
|
||||
// TODO replace with Filter(Route) bool
|
||||
type QueryOptions struct {
|
||||
// Service is destination service name
|
||||
Service string
|
||||
type LookupOptions struct {
|
||||
// Address of the service
|
||||
Address string
|
||||
// Gateway is route gateway
|
||||
@@ -20,53 +18,45 @@ type QueryOptions struct {
|
||||
Link string
|
||||
}
|
||||
|
||||
// QueryService sets service to query
|
||||
func QueryService(s string) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
o.Service = s
|
||||
}
|
||||
}
|
||||
|
||||
// QueryAddress sets service to query
|
||||
func QueryAddress(a string) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
// LookupAddress sets service to query
|
||||
func LookupAddress(a string) LookupOption {
|
||||
return func(o *LookupOptions) {
|
||||
o.Address = a
|
||||
}
|
||||
}
|
||||
|
||||
// QueryGateway sets gateway address to query
|
||||
func QueryGateway(g string) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
// LookupGateway sets gateway address to query
|
||||
func LookupGateway(g string) LookupOption {
|
||||
return func(o *LookupOptions) {
|
||||
o.Gateway = g
|
||||
}
|
||||
}
|
||||
|
||||
// QueryNetwork sets network name to query
|
||||
func QueryNetwork(n string) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
// LookupNetwork sets network name to query
|
||||
func LookupNetwork(n string) LookupOption {
|
||||
return func(o *LookupOptions) {
|
||||
o.Network = n
|
||||
}
|
||||
}
|
||||
|
||||
// QueryRouter sets router id to query
|
||||
func QueryRouter(r string) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
// LookupRouter sets router id to query
|
||||
func LookupRouter(r string) LookupOption {
|
||||
return func(o *LookupOptions) {
|
||||
o.Router = r
|
||||
}
|
||||
}
|
||||
|
||||
// QueryLink sets the link to query
|
||||
func QueryLink(link string) QueryOption {
|
||||
return func(o *QueryOptions) {
|
||||
// LookupLink sets the link to query
|
||||
func LookupLink(link string) LookupOption {
|
||||
return func(o *LookupOptions) {
|
||||
o.Link = link
|
||||
}
|
||||
}
|
||||
|
||||
// NewQuery creates new query and returns it
|
||||
func NewQuery(opts ...QueryOption) QueryOptions {
|
||||
// NewLookup creates new query and returns it
|
||||
func NewLookup(opts ...LookupOption) LookupOptions {
|
||||
// default options
|
||||
qopts := QueryOptions{
|
||||
Service: "*",
|
||||
qopts := LookupOptions{
|
||||
Address: "*",
|
||||
Gateway: "*",
|
||||
Network: "*",
|
||||
@@ -80,3 +70,66 @@ func NewQuery(opts ...QueryOption) QueryOptions {
|
||||
|
||||
return qopts
|
||||
}
|
||||
|
||||
// isMatch checks if the route matches given query options
|
||||
func isMatch(route Route, address, gateway, network, rtr, link string) bool {
|
||||
// matches the values provided
|
||||
match := func(a, b string) bool {
|
||||
if a == "*" || b == "*" || a == b {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// a simple struct to hold our values
|
||||
type compare struct {
|
||||
a string
|
||||
b string
|
||||
}
|
||||
|
||||
// compare the following values
|
||||
values := []compare{
|
||||
{gateway, route.Gateway},
|
||||
{network, route.Network},
|
||||
{rtr, route.Router},
|
||||
{address, route.Address},
|
||||
{link, route.Link},
|
||||
}
|
||||
|
||||
for _, v := range values {
|
||||
// attempt to match each value
|
||||
if !match(v.a, v.b) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// filterRoutes finds all the routes for given network and router and returns them
|
||||
func Filter(routes []Route, opts LookupOptions) []Route {
|
||||
address := opts.Address
|
||||
gateway := opts.Gateway
|
||||
network := opts.Network
|
||||
rtr := opts.Router
|
||||
link := opts.Link
|
||||
|
||||
// routeMap stores the routes we're going to advertise
|
||||
routeMap := make(map[string][]Route)
|
||||
|
||||
for _, route := range routes {
|
||||
if isMatch(route, address, gateway, network, rtr, link) {
|
||||
// add matchihg route to the routeMap
|
||||
routeKey := route.Service + "@" + route.Network
|
||||
routeMap[routeKey] = append(routeMap[routeKey], route)
|
||||
}
|
||||
}
|
||||
|
||||
var results []Route
|
||||
|
||||
for _, route := range routeMap {
|
||||
results = append(results, route...)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ func NewRouter(opts ...router.Option) router.Router {
|
||||
|
||||
// create the new table, passing the fetchRoute method in as a fallback if
|
||||
// the table doesn't contain the result for a query.
|
||||
r.table = newTable(r.lookup)
|
||||
r.table = newTable()
|
||||
|
||||
// start the router
|
||||
r.start()
|
||||
@@ -136,7 +136,7 @@ func (r *rtr) createRoutes(service *registry.Service, network string) []router.R
|
||||
Network: network,
|
||||
Router: r.options.Id,
|
||||
Link: router.DefaultLink,
|
||||
Metric: router.DefaultLocalMetric,
|
||||
Metric: router.DefaultMetric,
|
||||
Metadata: node.Metadata,
|
||||
})
|
||||
}
|
||||
@@ -241,8 +241,41 @@ func (r *rtr) loadRoutes(reg registry.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the router
|
||||
func (r *rtr) Close() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
select {
|
||||
case <-r.exit:
|
||||
return nil
|
||||
default:
|
||||
if !r.running {
|
||||
return nil
|
||||
}
|
||||
close(r.exit)
|
||||
|
||||
}
|
||||
|
||||
r.running = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookup retrieves all the routes for a given service and creates them in the routing table
|
||||
func (r *rtr) lookup(service string) ([]router.Route, error) {
|
||||
func (r *rtr) Lookup(service string, opts ...router.LookupOption) ([]router.Route, error) {
|
||||
q := router.NewLookup(opts...)
|
||||
|
||||
// if we find the routes filter and return them
|
||||
routes, err := r.table.Read(router.ReadService(service))
|
||||
if err == nil {
|
||||
routes = router.Filter(routes, q)
|
||||
if len(routes) == 0 {
|
||||
return nil, router.ErrRouteNotFound
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// lookup the route
|
||||
logger.Tracef("Fetching route for %s domain: %v", service, registry.WildcardDomain)
|
||||
|
||||
services, err := r.options.Registry.GetService(service, registry.GetDomain(registry.WildcardDomain))
|
||||
@@ -254,8 +287,6 @@ func (r *rtr) lookup(service string) ([]router.Route, error) {
|
||||
return nil, fmt.Errorf("failed getting services: %v", err)
|
||||
}
|
||||
|
||||
var routes []router.Route
|
||||
|
||||
for _, srv := range services {
|
||||
domain := getDomain(srv)
|
||||
// TODO: should we continue to send the event indicating we created a route?
|
||||
@@ -263,6 +294,17 @@ func (r *rtr) lookup(service string) ([]router.Route, error) {
|
||||
routes = append(routes, r.createRoutes(srv, domain)...)
|
||||
}
|
||||
|
||||
// if we're supposed to cache then save the routes
|
||||
if r.options.Cache {
|
||||
for _, route := range routes {
|
||||
r.table.Create(route)
|
||||
}
|
||||
}
|
||||
|
||||
routes = router.Filter(routes, q)
|
||||
if len(routes) == 0 {
|
||||
return nil, router.ErrRouteNotFound
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
@@ -324,13 +366,6 @@ func (r *rtr) start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.options.Precache {
|
||||
// add all local service routes into the routing table
|
||||
if err := r.loadRoutes(r.options.Registry); err != nil {
|
||||
return fmt.Errorf("failed loading registry routes: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// add default gateway into routing table
|
||||
if r.options.Gateway != "" {
|
||||
// note, the only non-default value is the gateway
|
||||
@@ -341,7 +376,7 @@ func (r *rtr) start() error {
|
||||
Network: "*",
|
||||
Router: r.options.Id,
|
||||
Link: router.DefaultLink,
|
||||
Metric: router.DefaultLocalMetric,
|
||||
Metric: router.DefaultMetric,
|
||||
}
|
||||
if err := r.table.Create(route); err != nil {
|
||||
return fmt.Errorf("failed adding default gateway route: %s", err)
|
||||
@@ -350,25 +385,59 @@ func (r *rtr) start() error {
|
||||
|
||||
// create error and exit channels
|
||||
r.exit = make(chan bool)
|
||||
r.running = true
|
||||
|
||||
// periodically refresh all the routes
|
||||
// only cache if told to do so
|
||||
if !r.options.Cache {
|
||||
return nil
|
||||
}
|
||||
|
||||
// create a refresh notify channel
|
||||
refresh := make(chan bool, 1)
|
||||
|
||||
// fires the refresh for loading routes
|
||||
refreshRoutes := func() {
|
||||
select {
|
||||
case refresh <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// refresh all the routes in the event of a failure watching the registry
|
||||
go func() {
|
||||
t1 := time.NewTicker(RefreshInterval)
|
||||
defer t1.Stop()
|
||||
var lastRefresh time.Time
|
||||
|
||||
t2 := time.NewTicker(PruneInterval)
|
||||
defer t2.Stop()
|
||||
// load a refresh
|
||||
refreshRoutes()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-r.exit:
|
||||
return
|
||||
case <-t2.C:
|
||||
r.table.pruneRoutes(RefreshInterval)
|
||||
case <-t1.C:
|
||||
case <-refresh:
|
||||
// don't refresh if we've done so in the past minute
|
||||
if !lastRefresh.IsZero() && time.Since(lastRefresh) < time.Minute {
|
||||
continue
|
||||
}
|
||||
|
||||
// load new routes
|
||||
if err := r.loadRoutes(r.options.Registry); err != nil {
|
||||
logger.Debugf("failed refreshing registry routes: %s", err)
|
||||
// in this don't prune
|
||||
continue
|
||||
}
|
||||
|
||||
// first time so nothing to prune
|
||||
if !lastRefresh.IsZero() {
|
||||
// prune any routes since last refresh since we've
|
||||
// updated basically everything we care about
|
||||
r.table.pruneRoutes(time.Since(lastRefresh))
|
||||
}
|
||||
|
||||
// update the refresh time
|
||||
lastRefresh = time.Now()
|
||||
case <-time.After(RefreshInterval):
|
||||
refreshRoutes()
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -386,6 +455,8 @@ func (r *rtr) start() error {
|
||||
logger.Debugf("failed creating registry watcher: %v", err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
// in the event of an error reload routes
|
||||
refreshRoutes()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -395,46 +466,21 @@ func (r *rtr) start() error {
|
||||
logger.Debugf("Error watching the registry: %v", err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
// in the event of an error reload routes
|
||||
refreshRoutes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
r.running = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup routes in the routing table
|
||||
func (r *rtr) Lookup(q ...router.QueryOption) ([]router.Route, error) {
|
||||
return r.Table().Query(q...)
|
||||
}
|
||||
|
||||
// Watch routes
|
||||
func (r *rtr) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
||||
return r.table.Watch(opts...)
|
||||
}
|
||||
|
||||
// Close the router
|
||||
func (r *rtr) Close() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
select {
|
||||
case <-r.exit:
|
||||
return nil
|
||||
default:
|
||||
if !r.running {
|
||||
return nil
|
||||
}
|
||||
close(r.exit)
|
||||
|
||||
}
|
||||
|
||||
r.running = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// String prints debugging information about router
|
||||
func (r *rtr) String() string {
|
||||
return "registry"
|
||||
|
@@ -12,8 +12,6 @@ import (
|
||||
// table is an in-memory routing table
|
||||
type table struct {
|
||||
sync.RWMutex
|
||||
// lookup for a service
|
||||
lookup func(string) ([]router.Route, error)
|
||||
// routes stores service routes
|
||||
routes map[string]map[uint64]*route
|
||||
// watchers stores table watchers
|
||||
@@ -26,9 +24,8 @@ type route struct {
|
||||
}
|
||||
|
||||
// newtable creates a new routing table and returns it
|
||||
func newTable(lookup func(string) ([]router.Route, error), opts ...router.Option) *table {
|
||||
func newTable() *table {
|
||||
return &table{
|
||||
lookup: lookup,
|
||||
routes: make(map[string]map[uint64]*route),
|
||||
watchers: make(map[string]*tableWatcher),
|
||||
}
|
||||
@@ -201,153 +198,40 @@ func (t *table) Update(r router.Route) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns a list of all routes in the table
|
||||
func (t *table) List() ([]router.Route, error) {
|
||||
// Read entries from the table
|
||||
func (t *table) Read(opts ...router.ReadOption) ([]router.Route, error) {
|
||||
var options router.ReadOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
var routes []router.Route
|
||||
for _, rmap := range t.routes {
|
||||
for _, route := range rmap {
|
||||
routes = append(routes, route.route)
|
||||
|
||||
// get the routes based on options passed
|
||||
if len(options.Service) > 0 {
|
||||
routeMap, ok := t.routes[options.Service]
|
||||
if !ok {
|
||||
return nil, router.ErrRouteNotFound
|
||||
}
|
||||
for _, rt := range routeMap {
|
||||
routes = append(routes, rt.route)
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// otherwise get all routes
|
||||
for _, serviceRoutes := range t.routes {
|
||||
for _, rt := range serviceRoutes {
|
||||
routes = append(routes, rt.route)
|
||||
}
|
||||
}
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// isMatch checks if the route matches given query options
|
||||
func isMatch(route router.Route, address, gateway, network, rtr, link string) bool {
|
||||
// matches the values provided
|
||||
match := func(a, b string) bool {
|
||||
if a == "*" || b == "*" || a == b {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// a simple struct to hold our values
|
||||
type compare struct {
|
||||
a string
|
||||
b string
|
||||
}
|
||||
|
||||
// compare the following values
|
||||
values := []compare{
|
||||
{gateway, route.Gateway},
|
||||
{network, route.Network},
|
||||
{rtr, route.Router},
|
||||
{address, route.Address},
|
||||
{link, route.Link},
|
||||
}
|
||||
|
||||
for _, v := range values {
|
||||
// attempt to match each value
|
||||
if !match(v.a, v.b) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// filterRoutes finds all the routes for given network and router and returns them
|
||||
func filterRoutes(routes map[uint64]*route, opts router.QueryOptions) []router.Route {
|
||||
address := opts.Address
|
||||
gateway := opts.Gateway
|
||||
network := opts.Network
|
||||
rtr := opts.Router
|
||||
link := opts.Link
|
||||
|
||||
// routeMap stores the routes we're going to advertise
|
||||
routeMap := make(map[string][]router.Route)
|
||||
|
||||
for _, rt := range routes {
|
||||
// get the actual route
|
||||
route := rt.route
|
||||
|
||||
if isMatch(route, address, gateway, network, rtr, link) {
|
||||
// add matchihg route to the routeMap
|
||||
routeKey := route.Service + "@" + route.Network
|
||||
routeMap[routeKey] = append(routeMap[routeKey], route)
|
||||
}
|
||||
}
|
||||
|
||||
var results []router.Route
|
||||
|
||||
for _, route := range routeMap {
|
||||
results = append(results, route...)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// Lookup queries routing table and returns all routes that match the lookup query
|
||||
func (t *table) Query(q ...router.QueryOption) ([]router.Route, error) {
|
||||
// create new query options
|
||||
opts := router.NewQuery(q...)
|
||||
|
||||
// create a cwslicelist of query results
|
||||
results := make([]router.Route, 0, len(t.routes))
|
||||
|
||||
// readAndFilter routes for this service under read lock.
|
||||
readAndFilter := func(q router.QueryOptions) ([]router.Route, bool) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
routes, ok := t.routes[q.Service]
|
||||
if !ok || len(routes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return filterRoutes(routes, q), true
|
||||
}
|
||||
|
||||
if opts.Service != "*" {
|
||||
// try and load services from the cache
|
||||
if routes, ok := readAndFilter(opts); ok {
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// lookup the route and try again
|
||||
// TODO: move this logic out of the hot path
|
||||
// being hammered on queries will require multiple lookups
|
||||
routes, err := t.lookup(opts.Service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// cache the routes
|
||||
for _, rt := range routes {
|
||||
t.Create(rt)
|
||||
}
|
||||
|
||||
// try again
|
||||
if routes, ok := readAndFilter(opts); ok {
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
return nil, router.ErrRouteNotFound
|
||||
}
|
||||
|
||||
// search through all destinations
|
||||
t.RLock()
|
||||
|
||||
for _, routes := range t.routes {
|
||||
// filter the routes
|
||||
found := filterRoutes(routes, opts)
|
||||
// ensure we don't append zero length routes
|
||||
if len(found) == 0 {
|
||||
continue
|
||||
}
|
||||
results = append(results, found...)
|
||||
}
|
||||
|
||||
t.RUnlock()
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Watch returns routing table entry watcher
|
||||
func (t *table) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
||||
// by default watch everything
|
||||
|
@@ -1,15 +1,13 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v3/router"
|
||||
)
|
||||
|
||||
func testSetup() (*table, router.Route) {
|
||||
routr := NewRouter().(*rtr)
|
||||
table := newTable(routr.lookup)
|
||||
table := newTable()
|
||||
|
||||
route := router.Route{
|
||||
Service: "dest.svc",
|
||||
@@ -101,7 +99,7 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
routes, err := table.List()
|
||||
routes, err := table.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("error listing routes: %s", err)
|
||||
}
|
||||
@@ -114,235 +112,20 @@ func TestList(t *testing.T) {
|
||||
func TestQuery(t *testing.T) {
|
||||
table, route := testSetup()
|
||||
|
||||
svc := []string{"svc1", "svc2", "svc3", "svc1"}
|
||||
net := []string{"net1", "net2", "net1", "net3"}
|
||||
gw := []string{"gw1", "gw2", "gw3", "gw3"}
|
||||
rtr := []string{"rtr1", "rt2", "rt3", "rtr3"}
|
||||
|
||||
for i := 0; i < len(svc); i++ {
|
||||
route.Service = svc[i]
|
||||
route.Network = net[i]
|
||||
route.Gateway = gw[i]
|
||||
route.Router = rtr[i]
|
||||
route.Link = router.DefaultLink
|
||||
|
||||
if err := table.Create(route); err != nil {
|
||||
t.Fatalf("error adding route: %s", err)
|
||||
}
|
||||
if err := table.Create(route); err != nil {
|
||||
t.Fatalf("error adding route: %s", err)
|
||||
}
|
||||
|
||||
// return all routes
|
||||
routes, err := table.Query()
|
||||
rt, err := table.Read(router.ReadService(route.Service))
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up routes: %s", err)
|
||||
} else if len(routes) == 0 {
|
||||
t.Fatalf("error looking up routes: not found")
|
||||
t.Fatal("Expected a route got err", err)
|
||||
}
|
||||
|
||||
// query routes particular network
|
||||
network := "net1"
|
||||
|
||||
routes, err = table.Query(router.QueryNetwork(network))
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up routes: %s", err)
|
||||
if len(rt) != 1 {
|
||||
t.Fatalf("Expected one route got %d", len(rt))
|
||||
}
|
||||
|
||||
if len(routes) != 2 {
|
||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 2, len(routes))
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Network != network {
|
||||
t.Fatalf("incorrect route returned. Expected network: %s, found: %s", network, route.Network)
|
||||
}
|
||||
}
|
||||
|
||||
// query routes for particular gateway
|
||||
gateway := "gw1"
|
||||
|
||||
routes, err = table.Query(router.QueryGateway(gateway))
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up routes: %s", err)
|
||||
}
|
||||
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes))
|
||||
}
|
||||
|
||||
if routes[0].Gateway != gateway {
|
||||
t.Fatalf("incorrect route returned. Expected gateway: %s, found: %s", gateway, routes[0].Gateway)
|
||||
}
|
||||
|
||||
// query routes for particular router
|
||||
rt := "rtr1"
|
||||
|
||||
routes, err = table.Query(router.QueryRouter(rt))
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up routes: %s", err)
|
||||
}
|
||||
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes))
|
||||
}
|
||||
|
||||
if routes[0].Router != rt {
|
||||
t.Fatalf("incorrect route returned. Expected router: %s, found: %s", rt, routes[0].Router)
|
||||
}
|
||||
|
||||
// query particular gateway and network
|
||||
query := []router.QueryOption{
|
||||
router.QueryGateway(gateway),
|
||||
router.QueryNetwork(network),
|
||||
router.QueryRouter(rt),
|
||||
}
|
||||
|
||||
routes, err = table.Query(query...)
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up routes: %s", err)
|
||||
}
|
||||
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes))
|
||||
}
|
||||
|
||||
if routes[0].Gateway != gateway {
|
||||
t.Fatalf("incorrect route returned. Expected gateway: %s, found: %s", gateway, routes[0].Gateway)
|
||||
}
|
||||
|
||||
if routes[0].Network != network {
|
||||
t.Fatalf("incorrect network returned. Expected network: %s, found: %s", network, routes[0].Network)
|
||||
}
|
||||
|
||||
if routes[0].Router != rt {
|
||||
t.Fatalf("incorrect route returned. Expected router: %s, found: %s", rt, routes[0].Router)
|
||||
}
|
||||
|
||||
// non-existen route query
|
||||
routes, err = table.Query(router.QueryService("foobar"))
|
||||
if err != router.ErrRouteNotFound {
|
||||
t.Fatalf("error looking up routes. Expected: %s, found: %s", router.ErrRouteNotFound, err)
|
||||
}
|
||||
|
||||
if len(routes) != 0 {
|
||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 0, len(routes))
|
||||
}
|
||||
|
||||
// query NO routes
|
||||
query = []router.QueryOption{
|
||||
router.QueryGateway(gateway),
|
||||
router.QueryNetwork(network),
|
||||
router.QueryLink("network"),
|
||||
}
|
||||
|
||||
routes, err = table.Query(query...)
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up routes: %s", err)
|
||||
}
|
||||
|
||||
if len(routes) > 0 {
|
||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 0, len(routes))
|
||||
}
|
||||
|
||||
// insert local routes to query
|
||||
for i := 0; i < 2; i++ {
|
||||
route.Link = "foobar"
|
||||
route.Address = fmt.Sprintf("local.route.address-%d", i)
|
||||
if err := table.Create(route); err != nil {
|
||||
t.Fatalf("error adding route: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// query local routes
|
||||
query = []router.QueryOption{
|
||||
router.QueryGateway("*"),
|
||||
router.QueryNetwork("*"),
|
||||
router.QueryLink("foobar"),
|
||||
}
|
||||
|
||||
routes, err = table.Query(query...)
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up routes: %s", err)
|
||||
}
|
||||
|
||||
if len(routes) != 2 {
|
||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 2, len(routes))
|
||||
}
|
||||
|
||||
// add two different routes for svcX with different metric
|
||||
for i := 0; i < 2; i++ {
|
||||
route.Service = "svcX"
|
||||
route.Address = fmt.Sprintf("svcX.route.address-%d", i)
|
||||
route.Metric = int64(100 + i)
|
||||
route.Link = router.DefaultLink
|
||||
if err := table.Create(route); err != nil {
|
||||
t.Fatalf("error adding route: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
query = []router.QueryOption{
|
||||
router.QueryService("svcX"),
|
||||
}
|
||||
|
||||
routes, err = table.Query(query...)
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up routes: %s", err)
|
||||
}
|
||||
|
||||
if len(routes) != 2 {
|
||||
t.Fatalf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes))
|
||||
if rt[0].Hash() != route.Hash() {
|
||||
t.Fatal("Mismatched routes received")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallback(t *testing.T) {
|
||||
|
||||
r := &rtr{
|
||||
options: router.DefaultOptions(),
|
||||
}
|
||||
route := router.Route{
|
||||
Service: "go.micro.service.foo",
|
||||
Router: r.options.Id,
|
||||
Link: router.DefaultLink,
|
||||
Metric: router.DefaultLocalMetric,
|
||||
}
|
||||
r.table = newTable(func(s string) ([]router.Route, error) {
|
||||
return []router.Route{route}, nil
|
||||
})
|
||||
r.start()
|
||||
|
||||
rts, err := r.Lookup(router.QueryService("go.micro.service.foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up service %s", err)
|
||||
}
|
||||
if len(rts) != 1 {
|
||||
t.Fatalf("incorrect number of routes returned %d", len(rts))
|
||||
}
|
||||
|
||||
// deleting from the table but the next query should invoke the fallback that we passed during new table creation
|
||||
if err := r.table.Delete(route); err != nil {
|
||||
t.Fatalf("error deleting route %s", err)
|
||||
}
|
||||
|
||||
rts, err = r.Lookup(router.QueryService("go.micro.service.foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("error looking up service %s", err)
|
||||
}
|
||||
if len(rts) != 1 {
|
||||
t.Fatalf("incorrect number of routes returned %d", len(rts))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFallbackError(t *testing.T) {
|
||||
r := &rtr{
|
||||
options: router.DefaultOptions(),
|
||||
}
|
||||
r.table = newTable(func(s string) ([]router.Route, error) {
|
||||
return nil, fmt.Errorf("ERROR")
|
||||
})
|
||||
r.start()
|
||||
_, err := r.Lookup(router.QueryService("go.micro.service.foo"))
|
||||
if err == nil {
|
||||
t.Fatalf("expected error looking up service but none returned")
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,40 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultLink is default network link
|
||||
DefaultLink = "local"
|
||||
// DefaultLocalMetric is default route cost for a local route
|
||||
DefaultLocalMetric int64 = 1
|
||||
)
|
||||
|
||||
// Route is network route
|
||||
type Route struct {
|
||||
// Service is destination service name
|
||||
Service string
|
||||
// Address is service node address
|
||||
Address string
|
||||
// Gateway is route gateway
|
||||
Gateway string
|
||||
// Network is network address
|
||||
Network string
|
||||
// Router is router id
|
||||
Router string
|
||||
// Link is network link
|
||||
Link string
|
||||
// Metric is the route cost metric
|
||||
Metric int64
|
||||
// Metadata for the route
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// Hash returns route hash sum.
|
||||
func (r *Route) Hash() uint64 {
|
||||
h := fnv.New64()
|
||||
h.Reset()
|
||||
h.Write([]byte(r.Service + r.Address + r.Gateway + r.Network + r.Router + r.Link))
|
||||
return h.Sum64()
|
||||
}
|
@@ -3,9 +3,14 @@ package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultLink is default network link
|
||||
DefaultLink = "local"
|
||||
// DefaultLocalMetric is default route cost for a local route
|
||||
DefaultMetric int64 = 1
|
||||
// DefaultNetwork is default micro network
|
||||
DefaultNetwork = "micro"
|
||||
// ErrRouteNotFound is returned when no route was found in the routing table
|
||||
@@ -23,7 +28,7 @@ type Router interface {
|
||||
// The routing table
|
||||
Table() Table
|
||||
// Lookup queries routes in the routing table
|
||||
Lookup(...QueryOption) ([]Route, error)
|
||||
Lookup(service string, opts ...LookupOption) ([]Route, error)
|
||||
// Watch returns a watcher which tracks updates to the routing table
|
||||
Watch(opts ...WatchOption) (Watcher, error)
|
||||
// Close the router
|
||||
@@ -40,10 +45,8 @@ type Table interface {
|
||||
Delete(Route) error
|
||||
// Update route in the routing table
|
||||
Update(Route) error
|
||||
// List all routes in the table
|
||||
List() ([]Route, error)
|
||||
// Query routes in the routing table
|
||||
Query(...QueryOption) ([]Route, error)
|
||||
// Read is for querying the table
|
||||
Read(...ReadOption) ([]Route, error)
|
||||
}
|
||||
|
||||
// Option used by the router
|
||||
@@ -60,3 +63,31 @@ const (
|
||||
// Error means the router has encountered error
|
||||
Error
|
||||
)
|
||||
|
||||
// Route is a network route
|
||||
type Route struct {
|
||||
// Service is destination service name
|
||||
Service string
|
||||
// Address is service node address
|
||||
Address string
|
||||
// Gateway is route gateway
|
||||
Gateway string
|
||||
// Network is network address
|
||||
Network string
|
||||
// Router is router id
|
||||
Router string
|
||||
// Link is network link
|
||||
Link string
|
||||
// Metric is the route cost metric
|
||||
Metric int64
|
||||
// Metadata for the route
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// Hash returns route hash sum.
|
||||
func (r *Route) Hash() uint64 {
|
||||
h := fnv.New64()
|
||||
h.Reset()
|
||||
h.Write([]byte(r.Service + r.Address + r.Gateway + r.Network + r.Router + r.Link))
|
||||
return h.Sum64()
|
||||
}
|
||||
|
@@ -1,21 +1,29 @@
|
||||
// Package static is a static router which returns the service name as the address + port
|
||||
package static
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/micro/go-micro/v3/router"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaulPort is the port to append where nothing is set
|
||||
DefaultPort = 8080
|
||||
)
|
||||
|
||||
// NewRouter returns an initialized static router
|
||||
func NewRouter(opts ...router.Option) router.Router {
|
||||
options := router.DefaultOptions()
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &static{options, new(table)}
|
||||
return &static{options}
|
||||
}
|
||||
|
||||
type static struct {
|
||||
options router.Options
|
||||
table router.Table
|
||||
}
|
||||
|
||||
func (s *static) Init(opts ...router.Option) error {
|
||||
@@ -33,8 +41,26 @@ func (s *static) Table() router.Table {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *static) Lookup(opts ...router.QueryOption) ([]router.Route, error) {
|
||||
return s.table.Query(opts...)
|
||||
func (s *static) Lookup(service string, opts ...router.LookupOption) ([]router.Route, error) {
|
||||
options := router.NewLookup(opts...)
|
||||
|
||||
_, _, err := net.SplitHostPort(service)
|
||||
if err == nil {
|
||||
// use the address
|
||||
options.Address = service
|
||||
} else {
|
||||
options.Address = fmt.Sprintf("%s:%d", service, DefaultPort)
|
||||
}
|
||||
|
||||
return []router.Route{
|
||||
router.Route{
|
||||
Service: service,
|
||||
Address: options.Address,
|
||||
Gateway: options.Gateway,
|
||||
Network: options.Network,
|
||||
Router: options.Router,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *static) Watch(opts ...router.WatchOption) (router.Watcher, error) {
|
||||
@@ -48,35 +74,3 @@ func (s *static) Close() error {
|
||||
func (s *static) String() string {
|
||||
return "static"
|
||||
}
|
||||
|
||||
type table struct{}
|
||||
|
||||
func (t *table) Create(router.Route) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) Delete(router.Route) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) Update(router.Route) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) List() ([]router.Route, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *table) Query(opts ...router.QueryOption) ([]router.Route, error) {
|
||||
options := router.NewQuery(opts...)
|
||||
|
||||
return []router.Route{
|
||||
router.Route{
|
||||
Address: options.Service,
|
||||
Service: options.Address,
|
||||
Gateway: options.Gateway,
|
||||
Network: options.Network,
|
||||
Router: options.Router,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
@@ -435,7 +435,7 @@ func (k *kubernetes) Create(s *runtime.Service, opts ...runtime.CreateOption) er
|
||||
if exist, err := k.namespaceExists(namespace); err == nil && !exist {
|
||||
if err := k.createNamespace(namespace); err != nil {
|
||||
if logger.V(logger.WarnLevel, logger.DefaultLogger) {
|
||||
logger.Warnf("Error creating namespacr %v: %v", namespace, err)
|
||||
logger.Warnf("Error creating namespace %v: %v", namespace, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -712,3 +712,35 @@ func credentialsName(service *runtime.Service) string {
|
||||
name := fmt.Sprintf("%v-%v-credentials", service.Name, service.Version)
|
||||
return client.SerializeResourceName(name)
|
||||
}
|
||||
|
||||
func (k *kubernetes) CreateNamespace(ns string) error {
|
||||
err := k.client.Create(&client.Resource{
|
||||
Kind: "namespace",
|
||||
Value: client.Namespace{
|
||||
Metadata: &client.Metadata{
|
||||
Name: ns,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("Error creating namespace %v: %v", ns, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (k *kubernetes) DeleteNamespace(ns string) error {
|
||||
err := k.client.Delete(&client.Resource{
|
||||
Kind: "namespace",
|
||||
Name: ns,
|
||||
})
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("Error deleting namespace %v: %v", ns, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
81
runtime/kubernetes/kubernetes_test.go
Normal file
81
runtime/kubernetes/kubernetes_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// +build kubernetes
|
||||
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func setupClient(t *testing.T) {
|
||||
files := []string{"token", "ca.crt"}
|
||||
for _, f := range files {
|
||||
cmd := exec.Command("kubectl", "get", "secrets", "-o",
|
||||
fmt.Sprintf(`jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='micro-runtime')].data.%s}"`,
|
||||
strings.ReplaceAll(f, ".", "\\.")))
|
||||
if outp, err := cmd.Output(); err != nil {
|
||||
t.Fatalf("Failed to set k8s token %s", err)
|
||||
} else {
|
||||
outq := outp[1 : len(outp)-1]
|
||||
decoded, err := base64.StdEncoding.DecodeString(string(outq))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set k8s token %s '%s'", err, outq)
|
||||
}
|
||||
if err := ioutil.WriteFile("/var/run/secrets/kubernetes.io/serviceaccount/"+f, decoded, 0755); err != nil {
|
||||
t.Fatalf("Error setting up k8s %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
outp, err := exec.Command("kubectl", "config", "view", "-o", `jsonpath='{.clusters[?(@.name=="kind-kind")].cluster.server}'`).Output()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot find server for kind %s", err)
|
||||
}
|
||||
serverHost := string(outp)
|
||||
|
||||
split := strings.Split(serverHost[9:len(serverHost)-1], ":")
|
||||
os.Setenv("KUBERNETES_SERVICE_HOST", split[0])
|
||||
os.Setenv("KUBERNETES_SERVICE_PORT", split[1])
|
||||
|
||||
}
|
||||
|
||||
func TestNamespaceCreateDelete(t *testing.T) {
|
||||
defer func() {
|
||||
exec.Command("kubectl", "delete", "namespace", "foobar").Run()
|
||||
}()
|
||||
setupClient(t)
|
||||
r := NewRuntime()
|
||||
if err := r.CreateNamespace("foobar"); err != nil {
|
||||
t.Fatalf("Unexpected error creating namespace %s", err)
|
||||
}
|
||||
|
||||
if !namespaceExists(t, "foobar") {
|
||||
t.Fatalf("Namespace foobar not found")
|
||||
}
|
||||
if err := r.DeleteNamespace("foobar"); err != nil {
|
||||
t.Fatalf("Unexpected error deleting namespace %s", err)
|
||||
}
|
||||
if namespaceExists(t, "foobar") {
|
||||
t.Fatalf("Namespace foobar still exists")
|
||||
}
|
||||
}
|
||||
|
||||
func namespaceExists(t *testing.T, ns string) bool {
|
||||
cmd := exec.Command("kubectl", "get", "namespaces")
|
||||
outp, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error listing namespaces %s", err)
|
||||
}
|
||||
exists, err := regexp.Match(ns+"\\s+Active", outp)
|
||||
if err != nil {
|
||||
t.Fatalf("Error listing namespaces %s", err)
|
||||
}
|
||||
return exists
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user