Compare commits
	
		
			39 Commits
		
	
	
		
			b6d2d459c5
			...
			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