Compare commits

...

39 Commits

Author SHA1 Message Date
Asim Aslam
f12473f4b1 Update runtime.go 2020-09-04 22:43:32 +01:00
Dominic Wong
724e2b5830 Memory events stream not pushing publications correctly (#1984)
* subs not filtered correctly on publish. simplify retry logic

* check fo invalid ackwait
2020-09-04 08:31:49 +01:00
Dominic Wong
6bdf33c4ee Event stream updates (#1981)
- auto and manual acking
- retry limits
2020-09-02 13:28:54 +01:00
dy1006
84f52fd7ac Update log.go (#1976)
change nlog.DefaultLogger.Log to nlog.DefaultLogger.Logf in the Logf function
2020-08-31 07:18:55 +01:00
Dominic Wong
6e30b53280 fix cockroach init to create table in correct database (#1977) 2020-08-28 10:35:47 +01:00
dy1006
a60426c884 Update rpc.go (#1975)
Co-authored-by: Asim Aslam <asim@aslam.me>
2020-08-27 10:24:19 +01:00
Prawn
2998735bf3 Tidying up the new Metrics implementations (#1974)
* Unit tests to check tagging and aggregation of Prometheus metrics

* Removing the logger output routing (because it doesn't actually work in the logger implementation)

* Emitting values with the logging reporter

Co-authored-by: chris <chris@Profanity.local>
2020-08-27 09:08:51 +01:00
wangxu
3a96135df8 add log grpc handler err (#1973)
Co-authored-by: wangxu <wangxu@oneniceapp.com>
2020-08-25 10:11:02 +01:00
zuoan
bf8b3aeac7 remove redunant code and cleanup (#1970)
* remove redundant code

* check invalid ip address first

* remove redundant code

* cleanup

Co-authored-by: 刘海洋 <haiyang@snqu.com>
2020-08-25 09:10:46 +01:00
Dominic Wong
5a52b5929c add create and delete namespace to runtime (#1965)
* add create and delete namespace to runtime

* dial down aggressive expiry

* add logging

* fix deletenamespace

* add start of k8s unit tests

* fix workflow

* turn on k8s tests

* ease tight tests

* mkdir in workflow

* dammit -p

* setup folder
2020-08-24 16:54:39 +01:00
ben-toogood
0adb469a85 runtime/local: fix unknown dir path (#1964)
Co-authored-by: Asim Aslam <asim@aslam.me>
Co-authored-by: Janos Dobronszki <dobronszki@gmail.com>
2020-08-24 15:23:44 +02:00
Asim Aslam
21004341bf Rename reporter.go to metrics.go 2020-08-23 22:18:28 +01:00
Asim Aslam
cc26f2b8b1 delete api service proto 2020-08-23 21:58:16 +01:00
Asim Aslam
1a6652fe6b sql model template 2020-08-23 21:30:46 +01:00
Asim Aslam
d28f0670d6 Move git into local/source 2020-08-23 21:23:07 +01:00
Asim Aslam
7bdd619e1b move plugin to util 2020-08-23 21:18:59 +01:00
Asim Aslam
c62d1d5eb8 move transport (#1967) 2020-08-23 18:37:22 +01:00
Asim Aslam
d60d85de5c move tunnel/resolver into network 2020-08-23 15:00:27 +01:00
Asim Aslam
44f281f8d9 remove test request file 2020-08-23 14:13:45 +01:00
Asim Aslam
f698feac9c remove context from client/server 2020-08-23 14:11:57 +01:00
Asim Aslam
f55701b374 Strip cache from the client. Its only used externally in a wrapper 2020-08-23 13:59:19 +01:00
Asim Aslam
82e8298b73 Router table.Read replaces List/Query (#1966)
* Table.REad insted of list and query

* fmt
2020-08-23 13:10:48 +01:00
Asim Aslam
fc54503232 Merge branch 'master' of ssh://github.com/micro/go-micro 2020-08-22 20:55:52 +01:00
Asim Aslam
6f0594eebe Update static router 2020-08-22 20:55:43 +01:00
Asim Aslam
6b52f859cf Update mucp.go 2020-08-22 16:36:03 +01:00
Asim Aslam
a3d4b8f79b Update mucp.go 2020-08-22 16:35:12 +01:00
Asim Aslam
7c7df6b35d Remove quic transport, move route into router 2020-08-22 16:15:44 +01:00
Asim Aslam
e80eab397a Update events.go 2020-08-22 09:20:53 +01:00
ben-toogood
6cda6ef92e runtime/local: add support for idiomatic folder structures (#1963)
* runtime/local: add support for idiomatic folder structures

* runtime/local: add test coverage

* runtime/local: increase test coverage

* runtime/local: add test for empty local source

* runtime/local: make entrypoint public
2020-08-21 11:17:42 +01:00
Prawn
f9f61d29de Observability/metrics update (#1962)
* Removing logging from the NOOP implementatino

* Simplifying the percentiles option

* Simple logging implementation

Co-authored-by: chris <chris@Profanity.local>
2020-08-21 20:57:10 +12:00
ben-toogood
1ae825032c store: remove write TTL & expiry options (#1960) 2020-08-21 09:35:53 +01:00
Asim Aslam
f146b52418 Registry router fixes (#1961)
* only cache routes if told to do so

* Use roundrobin selector and retry in proxy

* Update lookup to require service

* Fix compile

* Fix compile

* Update

* Update

* rename query to lookup

* Update router.go

* Update
2020-08-21 09:23:01 +01:00
Dominic Wong
78a79ca9e1 Memory and file store list fixes (#1959)
* Refactor file and memory stores
2020-08-20 15:08:35 +01:00
ben-toogood
329bc2f265 events: add store implementation (#1957) 2020-08-20 11:28:04 +01:00
Asim Aslam
8738ed7757 Update debug.go 2020-08-20 10:06:02 +01:00
ben-toogood
29e8cdbfe9 events: update interface (#1954) 2020-08-20 09:29:29 +01:00
Dominic Wong
47f356fc5f Unify the store tests (#1952)
Add more tests for store
2020-08-19 23:41:03 +01:00
Janos Dobronszki
21ffc73c4f Generic git checkout (#1951) 2020-08-19 17:24:42 +02:00
ben-toogood
81a9342b83 util/file: allow context to be passed (#1950) 2020-08-19 16:03:19 +01:00
148 changed files with 2549 additions and 3822 deletions

View File

@@ -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 ./...

View File

@@ -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()

View File

@@ -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

View File

@@ -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",
}

View File

@@ -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)
}

View File

@@ -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 {}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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")
}
})
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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"
)

View File

@@ -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 (

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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",
}

View File

@@ -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)
}

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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"
)

View File

@@ -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
}

View File

@@ -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()
})
}

View File

@@ -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()
})
}

View File

@@ -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
View 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
View 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
}

View 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")
})
}

View File

@@ -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
}
}

View File

@@ -0,0 +1 @@
package memory

View File

@@ -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")
}

View File

@@ -0,0 +1,3 @@
// +build nats
package nats

View 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
View File

@@ -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
View File

@@ -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=

View 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
}

View 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))
}

View File

@@ -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...),
}

View File

@@ -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
}
}

View File

@@ -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)
}

View File

@@ -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,
}
}

View File

@@ -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

View File

@@ -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`)
}

View File

@@ -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
View 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
View 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,
}
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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"
)

View File

@@ -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

View File

@@ -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

View File

@@ -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{}

View File

@@ -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"
)

View File

@@ -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

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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"
)

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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"
)

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 (

View File

@@ -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) {

View File

@@ -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

View File

@@ -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(),
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -5,7 +5,7 @@ import (
"errors"
"time"
"github.com/micro/go-micro/v3/transport"
"github.com/micro/go-micro/v3/network/transport"
)
const (

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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")
}
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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
}

View 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