diff --git a/.travis.yml b/.travis.yml index 3a827c94..211cb587 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: go go: -- 1.11.x - 1.12.x env: - GO111MODULE=on notifications: slack: secure: aEvhLbhujaGaKSrOokiG3//PaVHTIrc3fBpoRbCRqfZpyq6WREoapJJhF+tIpWWOwaC9GmChbD6aHo/jMUgwKXVyPSaNjiEL87YzUUpL8B2zslNp1rgfTg/LrzthOx3Q1TYwpaAl3to0fuHUVFX4yMeC2vuThq7WSXgMMxFCtbc= +cache: + directories: + - $GOPATH/pkg/mod diff --git a/agent/README.md b/agent/README.md deleted file mode 100644 index 76e6dc23..00000000 --- a/agent/README.md +++ /dev/null @@ -1,197 +0,0 @@ -# Agent - -Agent is a library used to create commands, inputs and robot services - -## Getting Started - -- [Commands](#commands) - Commands are functions executed by the bot based on text based pattern matching. -- [Inputs](#inputs) - Inputs are plugins for communication e.g Slack, Telegram, IRC, etc. -- [Services](#services) - Write bots as micro services - -## Commands - -Commands are functions executed by the bot based on text based pattern matching. - -### Write a Command - -```go -import "github.com/micro/go-micro/agent/command" - -func Ping() command.Command { - usage := "ping" - description := "Returns pong" - - return command.NewCommand("ping", usage, desc, func(args ...string) ([]byte, error) { - return []byte("pong"), nil - }) -} -``` - -### Register the command - -Add the command to the Commands map with a pattern key that can be matched by golang/regexp.Match - -```go -import "github.com/micro/go-micro/agent/command" - -func init() { - command.Commands["^ping$"] = Ping() -} -``` - -### Rebuild Micro - -Build binary -```shell -cd github.com/micro/micro - -// For local use -go build -i -o micro ./main.go - -// For docker image -CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' -i -o micro ./main.go -``` - -## Inputs - -Inputs are plugins for communication e.g Slack, HipChat, XMPP, IRC, SMTP, etc, etc. - -New inputs can be added in the following way. - -### Write an Input - -Write an input that satisfies the Input interface. - -```go -type Input interface { - // Provide cli flags - Flags() []cli.Flag - // Initialise input using cli context - Init(*cli.Context) error - // Stream events from the input - Stream() (Conn, error) - // Start the input - Start() error - // Stop the input - Stop() error - // name of the input - String() string -} -``` - -### Register the input - -Add the input to the Inputs map. - -```go -import "github.com/micro/micro/bot/input" - -func init() { - input.Inputs["name"] = MyInput -} -``` - -### Rebuild Micro - -Build binary -```shell -cd github.com/micro/micro - -// For local use -go build -i -o micro ./main.go - -// For docker image -CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' -i -o micro ./main.go -``` - -## Services - -The micro bot supports the ability to create commands as micro services. - -### How does it work? - -The bot watches the service registry for services with it's namespace. The default namespace is `go.micro.bot`. -Any service within this namespace will automatically be added to the list of available commands. When a command -is executed, the bot will call the service with method `Command.Exec`. It also expects the method `Command.Help` -to exist for usage info. - - -The service interface is as follows and can be found at [go-micro/agent/proto](https://github.com/micro/go-micro/agent/blob/master/proto/bot.proto) - -``` -syntax = "proto3"; - -package go.micro.bot; - -service Command { - rpc Help(HelpRequest) returns (HelpResponse) {}; - rpc Exec(ExecRequest) returns (ExecResponse) {}; -} - -message HelpRequest { -} - -message HelpResponse { - string usage = 1; - string description = 2; -} - -message ExecRequest { - repeated string args = 1; -} - -message ExecResponse { - bytes result = 1; - string error = 2; -} -``` - -### Example - -Here's an example echo command as a microservice - -```go -package main - -import ( - "fmt" - "strings" - - "github.com/micro/go-micro" - "golang.org/x/net/context" - - proto "github.com/micro/go-micro/agent/proto" -) - -type Command struct{} - -// Help returns the command usage -func (c *Command) Help(ctx context.Context, req *proto.HelpRequest, rsp *proto.HelpResponse) error { - // Usage should include the name of the command - rsp.Usage = "echo" - rsp.Description = "This is an example bot command as a micro service which echos the message" - return nil -} - -// Exec executes the command -func (c *Command) Exec(ctx context.Context, req *proto.ExecRequest, rsp *proto.ExecResponse) error { - rsp.Result = []byte(strings.Join(req.Args, " ")) - // rsp.Error could be set to return an error instead - // the function error would only be used for service level issues - return nil -} - -func main() { - service := micro.NewService( - micro.Name("go.micro.bot.echo"), - ) - - service.Init() - - proto.RegisterCommandHandler(service.Server(), new(Command)) - - if err := service.Run(); err != nil { - fmt.Println(err) - } -} -``` diff --git a/api/README.md b/api/README.md deleted file mode 100644 index 371d5ff3..00000000 --- a/api/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Go API [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/go-micro/api?status.svg)](https://godoc.org/github.com/micro/go-micro/api) [![Travis CI](https://api.travis-ci.org/micro/go-micro/api.svg?branch=master)](https://travis-ci.org/micro/go-micro/api) [![Go Report Card](https://goreportcard.com/badge/micro/go-micro/api)](https://goreportcard.com/report/github.com/micro/go-micro/api) - -Go API is a pluggable API framework driven by service discovery to help build powerful public API gateways. - -## Overview - -The Go API library provides api gateway routing capabilities. A microservice architecture decouples application logic into -separate service. An api gateway provides a single entry point to consolidate these services into a unified api. The -Go API uses routes defined in service discovery metadata to generate routing rules and serve http requests. - -Go API - -Go API is the basis for the [micro api](https://micro.mu/docs/api.html). - -## Getting Started - -See the [docs](https://micro.mu/docs/go-api.html) to learn more - diff --git a/api/handler/api/api.go b/api/handler/api/api.go index 20fa421d..0798f337 100644 --- a/api/handler/api/api.go +++ b/api/handler/api/api.go @@ -8,8 +8,8 @@ import ( "github.com/micro/go-micro/api/handler" api "github.com/micro/go-micro/api/proto" "github.com/micro/go-micro/client" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/errors" - "github.com/micro/go-micro/selector" "github.com/micro/go-micro/util/ctx" ) diff --git a/api/handler/api/util.go b/api/handler/api/util.go index 123ad3f8..7a4607c7 100644 --- a/api/handler/api/util.go +++ b/api/handler/api/util.go @@ -9,8 +9,8 @@ import ( "strings" api "github.com/micro/go-micro/api/proto" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" ) func requestToProto(r *http.Request) (*api.Request, error) { diff --git a/api/handler/broker/broker.go b/api/handler/broker/broker.go index a9f0800d..2d2905d9 100644 --- a/api/handler/broker/broker.go +++ b/api/handler/broker/broker.go @@ -120,7 +120,7 @@ func (c *conn) writeLoop() { opts = append(opts, broker.Queue(c.queue)) } - subscriber, err := c.b.Subscribe(c.topic, func(p broker.Publication) error { + subscriber, err := c.b.Subscribe(c.topic, func(p broker.Event) error { b, err := json.Marshal(p.Message()) if err != nil { return nil diff --git a/api/handler/http/http.go b/api/handler/http/http.go index cbcd2992..ad2b1c66 100644 --- a/api/handler/http/http.go +++ b/api/handler/http/http.go @@ -10,7 +10,7 @@ import ( "github.com/micro/go-micro/api" "github.com/micro/go-micro/api/handler" - "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/client/selector" ) const ( @@ -73,7 +73,7 @@ func (h *httpHandler) getService(r *http.Request) (string, error) { return "", nil } - return fmt.Sprintf("http://%s:%d", s.Address, s.Port), nil + return fmt.Sprintf("http://%s", s.Address), nil } func (h *httpHandler) String() string { diff --git a/api/handler/http/http_test.go b/api/handler/http/http_test.go index 37f3f2f1..31847221 100644 --- a/api/handler/http/http_test.go +++ b/api/handler/http/http_test.go @@ -4,14 +4,12 @@ import ( "net" "net/http" "net/http/httptest" - "strconv" - "strings" "testing" "github.com/micro/go-micro/api/handler" "github.com/micro/go-micro/api/router" regRouter "github.com/micro/go-micro/api/router/registry" - "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/config/cmd" "github.com/micro/go-micro/registry" "github.com/micro/go-micro/registry/memory" ) @@ -26,21 +24,12 @@ func testHttp(t *testing.T, path, service, ns string) { } defer l.Close() - parts := strings.Split(l.Addr().String(), ":") - - var host string - var port int - - host = parts[0] - port, _ = strconv.Atoi(parts[1]) - s := ®istry.Service{ Name: service, Nodes: []*registry.Node{ ®istry.Node{ Id: service + "-1", - Address: host, - Port: port, + Address: l.Addr().String(), }, }, } diff --git a/api/handler/rpc/rpc.go b/api/handler/rpc/rpc.go index 28c67245..0c01e10d 100644 --- a/api/handler/rpc/rpc.go +++ b/api/handler/rpc/rpc.go @@ -14,12 +14,12 @@ import ( "github.com/micro/go-micro/api/handler" proto "github.com/micro/go-micro/api/internal/proto" "github.com/micro/go-micro/client" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/codec" "github.com/micro/go-micro/codec/jsonrpc" "github.com/micro/go-micro/codec/protorpc" "github.com/micro/go-micro/errors" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" "github.com/micro/go-micro/util/ctx" ) @@ -120,32 +120,6 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var rsp []byte switch { - // json codecs - case hasCodec(ct, jsonCodecs): - var request json.RawMessage - // if the extracted payload isn't empty lets use it - if len(br) > 0 { - request = json.RawMessage(br) - } - - // create request/response - var response json.RawMessage - - req := c.NewRequest( - service.Name, - service.Endpoint.Name, - &request, - client.WithContentType(ct), - ) - - // make the call - if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil { - writeError(w, r, err) - return - } - - // marshall response - rsp, _ = response.MarshalJSON() // proto codecs case hasCodec(ct, protoCodecs): request := &proto.Message{} @@ -173,8 +147,36 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // marshall response rsp, _ = response.Marshal() default: - http.Error(w, "Unsupported Content-Type", 400) - return + // if json codec is not present set to json + if !hasCodec(ct, jsonCodecs) { + ct = "application/json" + } + + // default to trying json + var request json.RawMessage + // if the extracted payload isn't empty lets use it + if len(br) > 0 { + request = json.RawMessage(br) + } + + // create request/response + var response json.RawMessage + + req := c.NewRequest( + service.Name, + service.Endpoint.Name, + &request, + client.WithContentType(ct), + ) + + // make the call + if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil { + writeError(w, r, err) + return + } + + // marshall response + rsp, _ = response.MarshalJSON() } // write the response diff --git a/api/handler/web/web.go b/api/handler/web/web.go index 2a6ba18f..77a0f6af 100644 --- a/api/handler/web/web.go +++ b/api/handler/web/web.go @@ -13,7 +13,7 @@ import ( "github.com/micro/go-micro/api" "github.com/micro/go-micro/api/handler" - "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/client/selector" ) const ( @@ -79,7 +79,7 @@ func (wh *webHandler) getService(r *http.Request) (string, error) { return "", nil } - return fmt.Sprintf("http://%s:%d", s.Address, s.Port), nil + return fmt.Sprintf("http://%s", s.Address), nil } // serveWebSocket used to serve a web socket proxied connection diff --git a/api/router/options.go b/api/router/options.go index f41499a3..7285c74d 100644 --- a/api/router/options.go +++ b/api/router/options.go @@ -3,7 +3,7 @@ package router import ( "github.com/micro/go-micro/api/resolver" "github.com/micro/go-micro/api/resolver/micro" - "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/config/cmd" "github.com/micro/go-micro/registry" ) diff --git a/broker/broker.go b/broker/broker.go index 39db9f17..2871e818 100644 --- a/broker/broker.go +++ b/broker/broker.go @@ -3,28 +3,28 @@ package broker // Broker is an interface used for asynchronous messaging. type Broker interface { + Init(...Option) error Options() Options Address() string Connect() error Disconnect() error - Init(...Option) error - Publish(string, *Message, ...PublishOption) error - Subscribe(string, Handler, ...SubscribeOption) (Subscriber, error) + Publish(topic string, m *Message, opts ...PublishOption) error + Subscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) String() string } // Handler is used to process messages via a subscription of a topic. // The handler is passed a publication interface which contains the // message and optional Ack method to acknowledge receipt of the message. -type Handler func(Publication) error +type Handler func(Event) error type Message struct { Header map[string]string Body []byte } -// Publication is given to a subscription handler for processing -type Publication interface { +// Event is given to a subscription handler for processing +type Event interface { Topic() string Message() *Message Ack() error diff --git a/broker/common_test.go b/broker/common_test.go index af54db18..262a77eb 100644 --- a/broker/common_test.go +++ b/broker/common_test.go @@ -14,13 +14,11 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.0-123", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, { Id: "foo-1.0.0-321", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, }, }, @@ -30,8 +28,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.1-321", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, @@ -41,8 +38,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.3-345", - Address: "localhost", - Port: 8888, + Address: "localhost:8888", }, }, }, diff --git a/broker/http_broker.go b/broker/http_broker.go index 9f3f1012..5b362c14 100644 --- a/broker/http_broker.go +++ b/broker/http_broker.go @@ -13,8 +13,6 @@ import ( "net/http" "net/url" "runtime" - "strconv" - "strings" "sync" "time" @@ -59,7 +57,7 @@ type httpSubscriber struct { hb *httpBroker } -type httpPublication struct { +type httpEvent struct { m *Message t string } @@ -155,15 +153,15 @@ func newHttpBroker(opts ...Option) Broker { return h } -func (h *httpPublication) Ack() error { +func (h *httpEvent) Ack() error { return nil } -func (h *httpPublication) Message() *Message { +func (h *httpEvent) Message() *Message { return h.m } -func (h *httpPublication) Topic() string { +func (h *httpEvent) Topic() string { return h.t } @@ -323,7 +321,7 @@ func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - p := &httpPublication{m: m, t: topic} + p := &httpEvent{m: m, t: topic} id := req.Form.Get("id") h.RLock() @@ -403,6 +401,7 @@ func (h *httpBroker) Connect() error { go func() { h.run(l) h.Lock() + h.opts.Addrs = []string{addr} h.address = addr h.Unlock() }() @@ -542,7 +541,7 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption) vals := url.Values{} vals.Add("id", node.Id) - uri := fmt.Sprintf("%s://%s:%d%s?%s", scheme, node.Address, node.Port, DefaultSubPath, vals.Encode()) + uri := fmt.Sprintf("%s://%s%s?%s", scheme, node.Address, DefaultSubPath, vals.Encode()) r, err := h.c.Post(uri, "application/json", bytes.NewReader(b)) if err != nil { return err @@ -613,12 +612,15 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption) } func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) { + var err error + var host, port string options := NewSubscribeOptions(opts...) // parse address for host, port - parts := strings.Split(h.Address(), ":") - host := strings.Join(parts[:len(parts)-1], ":") - port, _ := strconv.Atoi(parts[len(parts)-1]) + host, port, err = net.SplitHostPort(h.Address()) + if err != nil { + return nil, err + } addr, err := maddr.Extract(host) if err != nil { @@ -637,8 +639,7 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO // register service node := ®istry.Node{ Id: id, - Address: addr, - Port: port, + Address: mnet.HostPort(addr, port), Metadata: map[string]string{ "secure": fmt.Sprintf("%t", secure), }, diff --git a/broker/http_broker_test.go b/broker/http_broker_test.go index 83db509b..4695033e 100644 --- a/broker/http_broker_test.go +++ b/broker/http_broker_test.go @@ -47,7 +47,7 @@ func sub(be *testing.B, c int) { done := make(chan bool, c) for i := 0; i < c; i++ { - sub, err := b.Subscribe(topic, func(p Publication) error { + sub, err := b.Subscribe(topic, func(p Event) error { done <- true m := p.Message() @@ -107,7 +107,7 @@ func pub(be *testing.B, c int) { done := make(chan bool, c*4) - sub, err := b.Subscribe(topic, func(p Publication) error { + sub, err := b.Subscribe(topic, func(p Event) error { done <- true m := p.Message() if string(m.Body) != string(msg.Body) { @@ -175,7 +175,7 @@ func TestBroker(t *testing.T) { done := make(chan bool) - sub, err := b.Subscribe("test", func(p Publication) error { + sub, err := b.Subscribe("test", func(p Event) error { m := p.Message() if string(m.Body) != string(msg.Body) { @@ -224,7 +224,7 @@ func TestConcurrentSubBroker(t *testing.T) { var wg sync.WaitGroup for i := 0; i < 10; i++ { - sub, err := b.Subscribe("test", func(p Publication) error { + sub, err := b.Subscribe("test", func(p Event) error { defer wg.Done() m := p.Message() @@ -279,7 +279,7 @@ func TestConcurrentPubBroker(t *testing.T) { var wg sync.WaitGroup - sub, err := b.Subscribe("test", func(p Publication) error { + sub, err := b.Subscribe("test", func(p Event) error { defer wg.Done() m := p.Message() diff --git a/broker/memory/memory.go b/broker/memory/memory.go index 7446bb21..effb22f3 100644 --- a/broker/memory/memory.go +++ b/broker/memory/memory.go @@ -3,21 +3,26 @@ package memory import ( "errors" + "math/rand" "sync" + "time" "github.com/google/uuid" "github.com/micro/go-micro/broker" + maddr "github.com/micro/go-micro/util/addr" + mnet "github.com/micro/go-micro/util/net" ) type memoryBroker struct { opts broker.Options + addr string sync.RWMutex connected bool Subscribers map[string][]*memorySubscriber } -type memoryPublication struct { +type memoryEvent struct { topic string message *broker.Message } @@ -35,7 +40,7 @@ func (m *memoryBroker) Options() broker.Options { } func (m *memoryBroker) Address() string { - return "" + return m.addr } func (m *memoryBroker) Connect() error { @@ -46,6 +51,15 @@ func (m *memoryBroker) Connect() error { return nil } + addr, err := maddr.Extract("::") + if err != nil { + return err + } + i := rand.Intn(20000) + // set addr with port + addr = mnet.HostPort(addr, 10000+i) + + m.addr = addr m.connected = true return nil @@ -72,19 +86,19 @@ func (m *memoryBroker) Init(opts ...broker.Option) error { } func (m *memoryBroker) Publish(topic string, message *broker.Message, opts ...broker.PublishOption) error { - m.Lock() - defer m.Unlock() - + m.RLock() if !m.connected { + m.RUnlock() return errors.New("not connected") } subs, ok := m.Subscribers[topic] + m.RUnlock() if !ok { return nil } - p := &memoryPublication{ + p := &memoryEvent{ topic: topic, message: message, } @@ -99,12 +113,12 @@ func (m *memoryBroker) Publish(topic string, message *broker.Message, opts ...br } func (m *memoryBroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) { - m.Lock() - defer m.Unlock() - + m.RLock() if !m.connected { + m.RUnlock() return nil, errors.New("not connected") } + m.RUnlock() var options broker.SubscribeOptions for _, o := range opts { @@ -119,7 +133,9 @@ func (m *memoryBroker) Subscribe(topic string, handler broker.Handler, opts ...b opts: options, } + m.Lock() m.Subscribers[topic] = append(m.Subscribers[topic], sub) + m.Unlock() go func() { <-sub.exit @@ -142,15 +158,15 @@ func (m *memoryBroker) String() string { return "memory" } -func (m *memoryPublication) Topic() string { +func (m *memoryEvent) Topic() string { return m.topic } -func (m *memoryPublication) Message() *broker.Message { +func (m *memoryEvent) Message() *broker.Message { return m.message } -func (m *memoryPublication) Ack() error { +func (m *memoryEvent) Ack() error { return nil } @@ -169,6 +185,7 @@ func (m *memorySubscriber) Unsubscribe() error { func NewBroker(opts ...broker.Option) broker.Broker { var options broker.Options + rand.Seed(time.Now().UnixNano()) for _, o := range opts { o(&options) } diff --git a/broker/memory/memory_test.go b/broker/memory/memory_test.go index 5c0021b1..625c7ee7 100644 --- a/broker/memory/memory_test.go +++ b/broker/memory/memory_test.go @@ -17,7 +17,7 @@ func TestMemoryBroker(t *testing.T) { topic := "test" count := 10 - fn := func(p broker.Publication) error { + fn := func(p broker.Event) error { return nil } diff --git a/broker/nats/nats.go b/broker/nats/nats.go index 027f1dac..eecb16ec 100644 --- a/broker/nats/nats.go +++ b/broker/nats/nats.go @@ -13,18 +13,19 @@ import ( ) type natsBroker struct { + sync.Once sync.RWMutex - addrs []string - conn *nats.Conn - opts broker.Options - nopts nats.Options - drain bool + addrs []string + conn *nats.Conn + opts broker.Options + nopts nats.Options + drain bool + closeCh chan (error) } type subscriber struct { - s *nats.Subscription - opts broker.SubscribeOptions - drain bool + s *nats.Subscription + opts broker.SubscribeOptions } type publication struct { @@ -54,9 +55,6 @@ func (s *subscriber) Topic() string { } func (s *subscriber) Unsubscribe() error { - if s.drain { - return s.s.Drain() - } return s.s.Unsubscribe() } @@ -122,20 +120,17 @@ func (n *natsBroker) Connect() error { func (n *natsBroker) Disconnect() error { n.RLock() + defer n.RUnlock() if n.drain { n.conn.Drain() - } else { - n.conn.Close() + return <-n.closeCh } - n.RUnlock() + n.conn.Close() return nil } func (n *natsBroker) Init(opts ...broker.Option) error { - for _, o := range opts { - o(&n.opts) - } - n.addrs = setAddrs(n.opts.Addrs) + n.setOption(opts...) return nil } @@ -167,11 +162,6 @@ func (n *natsBroker) Subscribe(topic string, handler broker.Handler, opts ...bro o(&opt) } - var drain bool - if _, ok := opt.Context.Value(drainSubscriptionKey{}).(bool); ok { - drain = true - } - fn := func(msg *nats.Msg) { var m broker.Message if err := n.opts.Codec.Unmarshal(msg.Data, &m); err != nil { @@ -193,7 +183,7 @@ func (n *natsBroker) Subscribe(topic string, handler broker.Handler, opts ...bro if err != nil { return nil, err } - return &subscriber{s: sub, opts: opt, drain: drain}, nil + return &subscriber{s: sub, opts: opt}, nil } func (n *natsBroker) String() string { @@ -207,39 +197,59 @@ func NewBroker(opts ...broker.Option) broker.Broker { Context: context.Background(), } + n := &natsBroker{ + opts: options, + } + n.setOption(opts...) + + return n +} + +func (n *natsBroker) setOption(opts ...broker.Option) { for _, o := range opts { - o(&options) + o(&n.opts) } - natsOpts := nats.GetDefaultOptions() - if n, ok := options.Context.Value(optionsKey{}).(nats.Options); ok { - natsOpts = n - } + n.Once.Do(func() { + n.nopts = nats.GetDefaultOptions() + }) - var drain bool - if _, ok := options.Context.Value(drainSubscriptionKey{}).(bool); ok { - drain = true + if nopts, ok := n.opts.Context.Value(optionsKey{}).(nats.Options); ok { + n.nopts = nopts } // broker.Options have higher priority than nats.Options // only if Addrs, Secure or TLSConfig were not set through a broker.Option // we read them from nats.Option - if len(options.Addrs) == 0 { - options.Addrs = natsOpts.Servers + if len(n.opts.Addrs) == 0 { + n.opts.Addrs = n.nopts.Servers } - if !options.Secure { - options.Secure = natsOpts.Secure + if !n.opts.Secure { + n.opts.Secure = n.nopts.Secure } - if options.TLSConfig == nil { - options.TLSConfig = natsOpts.TLSConfig + if n.opts.TLSConfig == nil { + n.opts.TLSConfig = n.nopts.TLSConfig } + n.addrs = setAddrs(n.opts.Addrs) - return &natsBroker{ - opts: options, - nopts: natsOpts, - addrs: setAddrs(options.Addrs), - drain: drain, + if n.opts.Context.Value(drainConnectionKey{}) != nil { + n.drain = true + n.closeCh = make(chan error) + n.nopts.ClosedCB = n.onClose + n.nopts.AsyncErrorCB = n.onAsyncError + } +} + +func (n *natsBroker) onClose(conn *nats.Conn) { + n.closeCh <- nil +} + +func (n *natsBroker) onAsyncError(conn *nats.Conn, sub *nats.Subscription, err error) { + // There are kinds of different async error nats might callback, but we are interested + // in ErrDrainTimeout only here. + if err == nats.ErrDrainTimeout { + n.closeCh <- err } } diff --git a/broker/nats/options.go b/broker/nats/options.go index 47431606..b5b106c0 100644 --- a/broker/nats/options.go +++ b/broker/nats/options.go @@ -7,7 +7,6 @@ import ( type optionsKey struct{} type drainConnectionKey struct{} -type drainSubscriptionKey struct{} // Options accepts nats.Options func Options(opts nats.Options) broker.Option { @@ -16,10 +15,5 @@ func Options(opts nats.Options) broker.Option { // DrainConnection will drain subscription on close func DrainConnection() broker.Option { - return setBrokerOption(drainConnectionKey{}, true) -} - -// DrainSubscription will drain pending messages when unsubscribe -func DrainSubscription() broker.SubscribeOption { - return setSubscribeOption(drainSubscriptionKey{}, true) + return setBrokerOption(drainConnectionKey{}, struct{}{}) } diff --git a/client/buffer.go b/client/buffer.go deleted file mode 100644 index 9eee2b3d..00000000 --- a/client/buffer.go +++ /dev/null @@ -1,14 +0,0 @@ -package client - -import ( - "bytes" -) - -type buffer struct { - *bytes.Buffer -} - -func (b *buffer) Close() error { - b.Buffer.Reset() - return nil -} diff --git a/client/client.go b/client/client.go index 80d7f49b..6d597342 100644 --- a/client/client.go +++ b/client/client.go @@ -10,7 +10,7 @@ import ( // Client is the interface used to make requests to services. // It supports Request/Response via Transport and Publishing via the Broker. -// It also supports bidiectional streaming of requests. +// It also supports bidirectional streaming of requests. type Client interface { Init(...Option) error Options() Options diff --git a/client/common_test.go b/client/common_test.go index d172812b..15ddc158 100644 --- a/client/common_test.go +++ b/client/common_test.go @@ -14,13 +14,11 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.0-123", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, { Id: "foo-1.0.0-321", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, }, }, @@ -30,8 +28,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.1-321", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, @@ -41,8 +38,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.3-345", - Address: "localhost", - Port: 8888, + Address: "localhost:8888", }, }, }, diff --git a/client/grpc/buffer.go b/client/grpc/buffer.go deleted file mode 100644 index c43bb231..00000000 --- a/client/grpc/buffer.go +++ /dev/null @@ -1,14 +0,0 @@ -package grpc - -import ( - "bytes" -) - -type buffer struct { - *bytes.Buffer -} - -func (b *buffer) Close() error { - b.Buffer.Reset() - return nil -} diff --git a/client/grpc/codec.go b/client/grpc/codec.go index 4994db70..e7891eef 100644 --- a/client/grpc/codec.go +++ b/client/grpc/codec.go @@ -2,12 +2,18 @@ package grpc import ( "fmt" + "strings" + b "bytes" + + "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" - "github.com/json-iterator/go" + jsoniter "github.com/json-iterator/go" "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/bytes" "github.com/micro/go-micro/codec/jsonrpc" "github.com/micro/go-micro/codec/protorpc" + "google.golang.org/grpc" "google.golang.org/grpc/encoding" ) @@ -16,6 +22,8 @@ type protoCodec struct{} type bytesCodec struct{} type wrapCodec struct{ encoding.Codec } +var jsonpbMarshaler = &jsonpb.Marshaler{} + var ( defaultGRPCCodecs = map[string]encoding.Codec{ "application/json": jsonCodec{}, @@ -52,7 +60,28 @@ func (w wrapCodec) String() string { return w.Codec.Name() } +func (w wrapCodec) Marshal(v interface{}) ([]byte, error) { + b, ok := v.(*bytes.Frame) + if ok { + return b.Data, nil + } + return w.Codec.Marshal(v) +} + +func (w wrapCodec) Unmarshal(data []byte, v interface{}) error { + b, ok := v.(*bytes.Frame) + if ok { + b.Data = data + return nil + } + return w.Codec.Unmarshal(data, v) +} + func (protoCodec) Marshal(v interface{}) ([]byte, error) { + b, ok := v.(*bytes.Frame) + if ok { + return b.Data, nil + } return proto.Marshal(v.(proto.Message)) } @@ -86,13 +115,79 @@ func (bytesCodec) Name() string { } func (jsonCodec) Marshal(v interface{}) ([]byte, error) { + if pb, ok := v.(proto.Message); ok { + s, err := jsonpbMarshaler.MarshalToString(pb) + + return []byte(s), err + } + return json.Marshal(v) } func (jsonCodec) Unmarshal(data []byte, v interface{}) error { + if pb, ok := v.(proto.Message); ok { + return jsonpb.Unmarshal(b.NewReader(data), pb) + } + return json.Unmarshal(data, v) } func (jsonCodec) Name() string { return "json" } + +type grpcCodec struct { + // headers + id string + target string + method string + endpoint string + + s grpc.ClientStream + c encoding.Codec +} + +func (g *grpcCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error { + md, err := g.s.Header() + if err != nil { + return err + } + if m == nil { + m = new(codec.Message) + } + if m.Header == nil { + m.Header = make(map[string]string) + } + for k, v := range md { + m.Header[k] = strings.Join(v, ",") + } + m.Id = g.id + m.Target = g.target + m.Method = g.method + m.Endpoint = g.endpoint + return nil +} + +func (g *grpcCodec) ReadBody(v interface{}) error { + if f, ok := v.(*bytes.Frame); ok { + return g.s.RecvMsg(f) + } + return g.s.RecvMsg(v) +} + +func (g *grpcCodec) Write(m *codec.Message, v interface{}) error { + // if we don't have a body + if v != nil { + return g.s.SendMsg(v) + } + // write the body using the framing codec + return g.s.SendMsg(&bytes.Frame{m.Body}) +} + +func (g *grpcCodec) Close() error { + return g.s.CloseSend() +} + +func (g *grpcCodec) String() string { + return g.c.Name() +} diff --git a/client/grpc/grpc.go b/client/grpc/grpc.go index 9ed206c6..c6a3f5dd 100644 --- a/client/grpc/grpc.go +++ b/client/grpc/grpc.go @@ -2,7 +2,6 @@ package grpc import ( - "bytes" "context" "crypto/tls" "fmt" @@ -12,13 +11,14 @@ import ( "github.com/micro/go-micro/broker" "github.com/micro/go-micro/client" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/codec" "github.com/micro/go-micro/errors" "github.com/micro/go-micro/metadata" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" "github.com/micro/go-micro/transport" + "github.com/micro/go-micro/util/buf" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding" @@ -32,8 +32,9 @@ type grpcClient struct { } func init() { - encoding.RegisterCodec(jsonCodec{}) - encoding.RegisterCodec(bytesCodec{}) + encoding.RegisterCodec(wrapCodec{jsonCodec{}}) + encoding.RegisterCodec(wrapCodec{protoCodec{}}) + encoding.RegisterCodec(wrapCodec{bytesCodec{}}) } // secure returns the dial option for whether its a secure or insecure connection @@ -58,14 +59,14 @@ func (g *grpcClient) next(request client.Request, opts client.CallOptions) (sele // get proxy address if prx := os.Getenv("MICRO_PROXY_ADDRESS"); len(prx) > 0 { - opts.Address = prx + opts.Address = []string{prx} } // return remote address if len(opts.Address) > 0 { return func() (*registry.Node, error) { return ®istry.Node{ - Address: opts.Address, + Address: opts.Address[0], }, nil }, nil } @@ -83,9 +84,6 @@ func (g *grpcClient) next(request client.Request, opts client.CallOptions) (sele func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error { address := node.Address - if node.Port > 0 { - address = fmt.Sprintf("%s:%d", address, node.Port) - } header := make(map[string]string) if md, ok := metadata.FromContext(ctx); ok { @@ -129,7 +127,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R ch := make(chan error, 1) go func() { - err := cc.Invoke(ctx, methodToGRPC(req.Endpoint(), req.Body()), req.Body(), rsp, grpc.ForceCodec(cf)) + err := cc.Invoke(ctx, methodToGRPC(req.Service(), req.Endpoint()), req.Body(), rsp, grpc.CallContentSubtype(cf.Name())) ch <- microError(err) }() @@ -145,9 +143,6 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client.Request, opts client.CallOptions) (client.Stream, error) { address := node.Address - if node.Port > 0 { - address = fmt.Sprintf("%s:%d", address, node.Port) - } header := make(map[string]string) if md, ok := metadata.FromContext(ctx); ok { @@ -177,7 +172,10 @@ func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client dialCtx, cancel = context.WithCancel(ctx) } defer cancel() - cc, err := grpc.DialContext(dialCtx, address, grpc.WithDefaultCallOptions(grpc.ForceCodec(cf)), g.secure()) + + wc := wrapCodec{cf} + + cc, err := grpc.DialContext(dialCtx, address, grpc.WithDefaultCallOptions(grpc.ForceCodec(wc)), g.secure()) if err != nil { return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err)) } @@ -188,15 +186,26 @@ func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client ServerStreams: true, } - st, err := cc.NewStream(ctx, desc, methodToGRPC(req.Endpoint(), req.Body())) + st, err := cc.NewStream(ctx, desc, methodToGRPC(req.Service(), req.Endpoint())) if err != nil { return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error creating stream: %v", err)) } + codec := &grpcCodec{ + s: st, + c: wc, + } + + // set request codec + if r, ok := req.(*grpcRequest); ok { + r.codec = codec + } + rsp := &response{ conn: cc, stream: st, codec: cf, + gcodec: codec, } return &grpcStream{ @@ -280,7 +289,7 @@ func (g *grpcClient) Options() client.Options { } func (g *grpcClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message { - return newGRPCPublication(topic, msg, "application/octet-stream") + return newGRPCEvent(topic, msg, g.opts.ContentType, opts...) } func (g *grpcClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request { @@ -357,9 +366,9 @@ func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface var gerr error for i := 0; i <= callOpts.Retries; i++ { - go func() { + go func(i int) { ch <- call(i) - }() + }(i) select { case <-ctx.Done(): @@ -440,10 +449,10 @@ func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...cli var grr error for i := 0; i <= callOpts.Retries; i++ { - go func() { + go func(i int) { s, err := call(i) ch <- response{s, err} - }() + }(i) select { case <-ctx.Done(): @@ -482,8 +491,9 @@ func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...clie return errors.InternalServerError("go.micro.client", err.Error()) } - b := &buffer{bytes.NewBuffer(nil)} - if err := cf(b).Write(&codec.Message{Type: codec.Publication}, p.Payload()); err != nil { + b := buf.New(nil) + + if err := cf(b).Write(&codec.Message{Type: codec.Event}, p.Payload()); err != nil { return errors.InternalServerError("go.micro.client", err.Error()) } diff --git a/client/grpc/grpc_test.go b/client/grpc/grpc_test.go index 574058cb..5696a267 100644 --- a/client/grpc/grpc_test.go +++ b/client/grpc/grpc_test.go @@ -3,14 +3,12 @@ package grpc import ( "context" "net" - "strconv" - "strings" "testing" "github.com/micro/go-micro/client" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/registry" "github.com/micro/go-micro/registry/memory" - "github.com/micro/go-micro/selector" pgrpc "google.golang.org/grpc" pb "google.golang.org/grpc/examples/helloworld/helloworld" ) @@ -36,22 +34,17 @@ func TestGRPCClient(t *testing.T) { go s.Serve(l) defer s.Stop() - parts := strings.Split(l.Addr().String(), ":") - port, _ := strconv.Atoi(parts[len(parts)-1]) - addr := strings.Join(parts[:len(parts)-1], ":") - // create mock registry r := memory.NewRegistry() // register service r.Register(®istry.Service{ - Name: "test", + Name: "helloworld", Version: "test", Nodes: []*registry.Node{ ®istry.Node{ Id: "test-1", - Address: addr, - Port: port, + Address: l.Addr().String(), }, }, }) @@ -73,7 +66,7 @@ func TestGRPCClient(t *testing.T) { } for _, method := range testMethods { - req := c.NewRequest("test", method, &pb.HelloRequest{ + req := c.NewRequest("helloworld", method, &pb.HelloRequest{ Name: "John", }) diff --git a/client/grpc/message.go b/client/grpc/message.go index 6938064e..5691868e 100644 --- a/client/grpc/message.go +++ b/client/grpc/message.go @@ -4,13 +4,13 @@ import ( "github.com/micro/go-micro/client" ) -type grpcPublication struct { +type grpcEvent struct { topic string contentType string payload interface{} } -func newGRPCPublication(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message { +func newGRPCEvent(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message { var options client.MessageOptions for _, o := range opts { o(&options) @@ -20,21 +20,21 @@ func newGRPCPublication(topic string, payload interface{}, contentType string, o contentType = options.ContentType } - return &grpcPublication{ + return &grpcEvent{ payload: payload, topic: topic, contentType: contentType, } } -func (g *grpcPublication) ContentType() string { +func (g *grpcEvent) ContentType() string { return g.contentType } -func (g *grpcPublication) Topic() string { +func (g *grpcEvent) Topic() string { return g.topic } -func (g *grpcPublication) Payload() interface{} { +func (g *grpcEvent) Payload() interface{} { return g.payload } diff --git a/client/grpc/request.go b/client/grpc/request.go index cb14a330..066577ce 100644 --- a/client/grpc/request.go +++ b/client/grpc/request.go @@ -2,7 +2,6 @@ package grpc import ( "fmt" - "reflect" "strings" "github.com/micro/go-micro/client" @@ -18,30 +17,25 @@ type grpcRequest struct { codec codec.Codec } -func methodToGRPC(method string, request interface{}) string { +// service Struct.Method /service.Struct/Method +func methodToGRPC(service, method string) string { // no method or already grpc method if len(method) == 0 || method[0] == '/' { return method } - // can't operate on nil request - t := reflect.TypeOf(request) - if t == nil { - return method - } - // dereference - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - // get package name - pParts := strings.Split(t.PkgPath(), "/") - pkg := pParts[len(pParts)-1] + // assume method is Foo.Bar mParts := strings.Split(method, ".") if len(mParts) != 2 { return method } + + if len(service) == 0 { + return fmt.Sprintf("/%s/%s", mParts[0], mParts[1]) + } + // return /pkg.Foo/Bar - return fmt.Sprintf("/%s.%s/%s", pkg, mParts[0], mParts[1]) + return fmt.Sprintf("/%s.%s/%s", service, mParts[0], mParts[1]) } func newGRPCRequest(service, method string, request interface{}, contentType string, reqOpts ...client.RequestOption) client.Request { diff --git a/client/grpc/request_test.go b/client/grpc/request_test.go index eab3a3a1..c73d675b 100644 --- a/client/grpc/request_test.go +++ b/client/grpc/request_test.go @@ -2,45 +2,38 @@ package grpc import ( "testing" - - pb "google.golang.org/grpc/examples/helloworld/helloworld" ) func TestMethodToGRPC(t *testing.T) { testData := []struct { + service string method string expect string - request interface{} }{ { + "helloworld", "Greeter.SayHello", "/helloworld.Greeter/SayHello", - new(pb.HelloRequest), }, { + "helloworld", "/helloworld.Greeter/SayHello", "/helloworld.Greeter/SayHello", - new(pb.HelloRequest), }, { + "", + "/helloworld.Greeter/SayHello", + "/helloworld.Greeter/SayHello", + }, + { + "", "Greeter.SayHello", - "/helloworld.Greeter/SayHello", - pb.HelloRequest{}, - }, - { - "/helloworld.Greeter/SayHello", - "/helloworld.Greeter/SayHello", - pb.HelloRequest{}, - }, - { - "Greeter.SayHello", - "Greeter.SayHello", - nil, + "/Greeter/SayHello", }, } for _, d := range testData { - method := methodToGRPC(d.method, d.request) + method := methodToGRPC(d.service, d.method) if method != d.expect { t.Fatalf("expected %s got %s", d.expect, method) } diff --git a/client/grpc/response.go b/client/grpc/response.go index 7ef72241..c870fad9 100644 --- a/client/grpc/response.go +++ b/client/grpc/response.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/bytes" "google.golang.org/grpc" "google.golang.org/grpc/encoding" ) @@ -12,11 +13,12 @@ type response struct { conn *grpc.ClientConn stream grpc.ClientStream codec encoding.Codec + gcodec codec.Codec } // Read the response func (r *response) Codec() codec.Reader { - return nil + return r.gcodec } // read the header @@ -34,5 +36,9 @@ func (r *response) Header() map[string]string { // Read the undecoded response func (r *response) Read() ([]byte, error) { - return nil, nil + f := &bytes.Frame{} + if err := r.gcodec.ReadBody(f); err != nil { + return nil, err + } + return f.Data, nil } diff --git a/client/options.go b/client/options.go index 30a6386c..9f363d74 100644 --- a/client/options.go +++ b/client/options.go @@ -5,9 +5,9 @@ import ( "time" "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/codec" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" "github.com/micro/go-micro/transport" ) @@ -43,8 +43,8 @@ type Options struct { type CallOptions struct { SelectOptions []selector.SelectOption - // Address of remote host - Address string + // Address of remote hosts + Address []string // Backoff func Backoff BackoffFunc // Check if retriable func @@ -245,8 +245,8 @@ func WithExchange(e string) PublishOption { } } -// WithAddress sets the remote address to use rather than using service discovery -func WithAddress(a string) CallOption { +// WithAddress sets the remote addresses to use rather than using service discovery +func WithAddress(a ...string) CallOption { return func(o *CallOptions) { o.Address = a } diff --git a/client/pool/default.go b/client/pool/default.go new file mode 100644 index 00000000..d625289c --- /dev/null +++ b/client/pool/default.go @@ -0,0 +1,114 @@ +package pool + +import ( + "sync" + "time" + + "github.com/google/uuid" + "github.com/micro/go-micro/transport" +) + +type pool struct { + size int + ttl time.Duration + tr transport.Transport + + sync.Mutex + conns map[string][]*poolConn +} + +type poolConn struct { + transport.Client + id string + created time.Time +} + +func newPool(options Options) *pool { + return &pool{ + size: options.Size, + tr: options.Transport, + ttl: options.TTL, + conns: make(map[string][]*poolConn), + } +} + +func (p *pool) Close() error { + p.Lock() + for k, c := range p.conns { + for _, conn := range c { + conn.Client.Close() + } + delete(p.conns, k) + } + p.Unlock() + return nil +} + +// NoOp the Close since we manage it +func (p *poolConn) Close() error { + return nil +} + +func (p *poolConn) Id() string { + return p.id +} + +func (p *poolConn) Created() time.Time { + return p.created +} + +func (p *pool) Get(addr string, opts ...transport.DialOption) (Conn, error) { + p.Lock() + conns := p.conns[addr] + + // while we have conns check age and then return one + // otherwise we'll create a new conn + for len(conns) > 0 { + conn := conns[len(conns)-1] + conns = conns[:len(conns)-1] + p.conns[addr] = conns + + // if conn is old kill it and move on + if d := time.Since(conn.Created()); d > p.ttl { + conn.Client.Close() + continue + } + + // we got a good conn, lets unlock and return it + p.Unlock() + + return conn, nil + } + + p.Unlock() + + // create new conn + c, err := p.tr.Dial(addr, opts...) + if err != nil { + return nil, err + } + return &poolConn{ + Client: c, + id: uuid.New().String(), + created: time.Now(), + }, nil +} + +func (p *pool) Release(conn Conn, err error) error { + // don't store the conn if it has errored + if err != nil { + return conn.(*poolConn).Client.Close() + } + + // otherwise put it back for reuse + p.Lock() + conns := p.conns[conn.Remote()] + if len(conns) >= p.size { + p.Unlock() + return conn.(*poolConn).Client.Close() + } + p.conns[conn.Remote()] = append(conns, conn.(*poolConn)) + p.Unlock() + + return nil +} diff --git a/client/rpc_pool_test.go b/client/pool/default_test.go similarity index 86% rename from client/rpc_pool_test.go rename to client/pool/default_test.go index 6c5875f1..4823e228 100644 --- a/client/rpc_pool_test.go +++ b/client/pool/default_test.go @@ -1,4 +1,4 @@ -package client +package pool import ( "testing" @@ -9,12 +9,17 @@ import ( ) func testPool(t *testing.T, size int, ttl time.Duration) { - // zero pool - p := newPool(size, ttl) - // mock transport tr := memory.NewTransport() + options := Options{ + TTL: ttl, + Size: size, + Transport: tr, + } + // zero pool + p := newPool(options) + // listen l, err := tr.Listen(":0") if err != nil { @@ -43,7 +48,7 @@ func testPool(t *testing.T, size int, ttl time.Duration) { for i := 0; i < 10; i++ { // get a conn - c, err := p.getConn(l.Addr(), tr) + c, err := p.Get(l.Addr()) if err != nil { t.Fatal(err) } @@ -67,7 +72,7 @@ func testPool(t *testing.T, size int, ttl time.Duration) { } // release the conn - p.release(l.Addr(), c, nil) + p.Release(c, nil) p.Lock() if i := len(p.conns[l.Addr()]); i > size { @@ -78,7 +83,7 @@ func testPool(t *testing.T, size int, ttl time.Duration) { } } -func TestRPCPool(t *testing.T) { +func TestClientPool(t *testing.T) { testPool(t, 0, time.Minute) testPool(t, 2, time.Minute) } diff --git a/client/pool/options.go b/client/pool/options.go new file mode 100644 index 00000000..ed3feda7 --- /dev/null +++ b/client/pool/options.go @@ -0,0 +1,33 @@ +package pool + +import ( + "time" + + "github.com/micro/go-micro/transport" +) + +type Options struct { + Transport transport.Transport + TTL time.Duration + Size int +} + +type Option func(*Options) + +func Size(i int) Option { + return func(o *Options) { + o.Size = i + } +} + +func Transport(t transport.Transport) Option { + return func(o *Options) { + o.Transport = t + } +} + +func TTL(t time.Duration) Option { + return func(o *Options) { + o.TTL = t + } +} diff --git a/client/pool/pool.go b/client/pool/pool.go new file mode 100644 index 00000000..79dd1a64 --- /dev/null +++ b/client/pool/pool.go @@ -0,0 +1,35 @@ +// Package pool is a connection pool +package pool + +import ( + "time" + + "github.com/micro/go-micro/transport" +) + +// Pool is an interface for connection pooling +type Pool interface { + // Close the pool + Close() error + // Get a connection + Get(addr string, opts ...transport.DialOption) (Conn, error) + // Releaes the connection + Release(c Conn, status error) error +} + +type Conn interface { + // unique id of connection + Id() string + // time it was created + Created() time.Time + // embedded connection + transport.Client +} + +func NewPool(opts ...Option) Pool { + var options Options + for _, o := range opts { + o(&options) + } + return newPool(options) +} diff --git a/client/proto/client.micro.go b/client/proto/client.micro.go new file mode 100644 index 00000000..d8c9ad88 --- /dev/null +++ b/client/proto/client.micro.go @@ -0,0 +1,203 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: micro/go-micro/client/proto/client.proto + +package go_micro_client + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +import ( + context "context" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/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 _ context.Context +var _ client.Option +var _ server.Option + +// Client API for Micro service + +type MicroService 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) (Micro_StreamService, error) + // Publish publishes a message and returns an empty Message + Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Message, error) +} + +type microService struct { + c client.Client + name string +} + +func NewMicroService(name string, c client.Client) MicroService { + if c == nil { + c = client.NewClient() + } + if len(name) == 0 { + name = "go.micro.client" + } + return µService{ + c: c, + name: name, + } +} + +func (c *microService) Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) { + req := c.c.NewRequest(c.name, "Micro.Call", in) + out := new(Response) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *microService) Stream(ctx context.Context, opts ...client.CallOption) (Micro_StreamService, error) { + req := c.c.NewRequest(c.name, "Micro.Stream", &Request{}) + stream, err := c.c.Stream(ctx, req, opts...) + if err != nil { + return nil, err + } + return µServiceStream{stream}, nil +} + +type Micro_StreamService interface { + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Send(*Request) error + Recv() (*Response, error) +} + +type microServiceStream struct { + stream client.Stream +} + +func (x *microServiceStream) Close() error { + return x.stream.Close() +} + +func (x *microServiceStream) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *microServiceStream) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *microServiceStream) Send(m *Request) error { + return x.stream.Send(m) +} + +func (x *microServiceStream) Recv() (*Response, error) { + m := new(Response) + err := x.stream.Recv(m) + if err != nil { + return nil, err + } + return m, nil +} + +func (c *microService) Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Message, error) { + req := c.c.NewRequest(c.name, "Micro.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 Micro service + +type MicroHandler interface { + // Call allows a single request to be made + Call(context.Context, *Request, *Response) error + // Stream is a bidirectional stream + Stream(context.Context, Micro_StreamStream) error + // Publish publishes a message and returns an empty Message + Publish(context.Context, *Message, *Message) error +} + +func RegisterMicroHandler(s server.Server, hdlr MicroHandler, opts ...server.HandlerOption) error { + type micro 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 Micro struct { + micro + } + h := µHandler{hdlr} + return s.Handle(s.NewHandler(&Micro{h}, opts...)) +} + +type microHandler struct { + MicroHandler +} + +func (h *microHandler) Call(ctx context.Context, in *Request, out *Response) error { + return h.MicroHandler.Call(ctx, in, out) +} + +func (h *microHandler) Stream(ctx context.Context, stream server.Stream) error { + return h.MicroHandler.Stream(ctx, µStreamStream{stream}) +} + +type Micro_StreamStream interface { + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Send(*Response) error + Recv() (*Request, error) +} + +type microStreamStream struct { + stream server.Stream +} + +func (x *microStreamStream) Close() error { + return x.stream.Close() +} + +func (x *microStreamStream) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *microStreamStream) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *microStreamStream) Send(m *Response) error { + return x.stream.Send(m) +} + +func (x *microStreamStream) Recv() (*Request, error) { + m := new(Request) + if err := x.stream.Recv(m); err != nil { + return nil, err + } + return m, nil +} + +func (h *microHandler) Publish(ctx context.Context, in *Message, out *Message) error { + return h.MicroHandler.Publish(ctx, in, out) +} diff --git a/client/proto/client.pb.go b/client/proto/client.pb.go new file mode 100644 index 00000000..2052f077 --- /dev/null +++ b/client/proto/client.pb.go @@ -0,0 +1,388 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: micro/go-micro/client/proto/client.proto + +package go_micro_client + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + 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_7d733ae29171347b, []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_7d733ae29171347b, []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_7d733ae29171347b, []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("micro/go-micro/client/proto/client.proto", fileDescriptor_7d733ae29171347b) +} + +var fileDescriptor_7d733ae29171347b = []byte{ + // 270 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x91, 0x3f, 0x4f, 0xc3, 0x30, + 0x10, 0xc5, 0xeb, 0xfe, 0x4b, 0x39, 0x2a, 0x21, 0x9d, 0x18, 0x4c, 0x06, 0x54, 0x32, 0x65, 0xc1, + 0x45, 0x30, 0x23, 0x86, 0xce, 0x95, 0x50, 0x40, 0xac, 0x28, 0x71, 0x4f, 0xc1, 0x52, 0x6a, 0x9b, + 0xd8, 0xad, 0x94, 0xef, 0xc8, 0x87, 0x42, 0x38, 0x29, 0x45, 0xd0, 0x2e, 0x6c, 0xf7, 0xee, 0x67, + 0xbd, 0x3b, 0xbf, 0x83, 0x74, 0xad, 0x64, 0x6d, 0xe6, 0xa5, 0xb9, 0x6e, 0x0b, 0x59, 0x29, 0xd2, + 0x7e, 0x6e, 0x6b, 0xe3, 0x77, 0x42, 0x04, 0x81, 0x67, 0xa5, 0x11, 0xe1, 0x8d, 0x68, 0xdb, 0xc9, + 0x16, 0xa2, 0x8c, 0xde, 0x37, 0xe4, 0x3c, 0x72, 0x88, 0x1c, 0xd5, 0x5b, 0x25, 0x89, 0xb3, 0x19, + 0x4b, 0x4f, 0xb2, 0x9d, 0xc4, 0x18, 0x26, 0xa4, 0x57, 0xd6, 0x28, 0xed, 0x79, 0x3f, 0xa0, 0x6f, + 0x8d, 0x57, 0x30, 0x95, 0x46, 0x7b, 0xd2, 0xfe, 0xd5, 0x37, 0x96, 0xf8, 0x20, 0xf0, 0xd3, 0xae, + 0xf7, 0xdc, 0x58, 0x42, 0x84, 0x61, 0x61, 0x56, 0x0d, 0x1f, 0xce, 0x58, 0x3a, 0xcd, 0x42, 0x9d, + 0x5c, 0xc2, 0x24, 0x23, 0x67, 0x8d, 0x76, 0x7b, 0xce, 0x7e, 0xf0, 0x17, 0x88, 0x96, 0xe4, 0x5c, + 0x5e, 0x12, 0x9e, 0xc3, 0xc8, 0x1b, 0xab, 0x64, 0xb7, 0x55, 0x2b, 0xfe, 0xcc, 0xed, 0x1f, 0x9f, + 0x3b, 0xd8, 0xfb, 0xde, 0x7e, 0x30, 0x18, 0x2d, 0xbf, 0x02, 0xc0, 0x7b, 0x18, 0x2e, 0xf2, 0xaa, + 0x42, 0x2e, 0x7e, 0x65, 0x22, 0xba, 0x40, 0xe2, 0x8b, 0x03, 0xa4, 0x5d, 0x39, 0xe9, 0xe1, 0x02, + 0xc6, 0x4f, 0xbe, 0xa6, 0x7c, 0xfd, 0x4f, 0x83, 0x94, 0xdd, 0x30, 0x7c, 0x80, 0xe8, 0x71, 0x53, + 0x54, 0xca, 0xbd, 0x1d, 0x70, 0xe9, 0xfe, 0x1f, 0x1f, 0x25, 0x49, 0xaf, 0x18, 0x87, 0xb3, 0xde, + 0x7d, 0x06, 0x00, 0x00, 0xff, 0xff, 0xd3, 0x63, 0x94, 0x1a, 0x02, 0x02, 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 + +// MicroClient is the client API for Micro service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MicroClient 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) (Micro_StreamClient, error) + // Publish publishes a message and returns an empty Message + Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) +} + +type microClient struct { + cc *grpc.ClientConn +} + +func NewMicroClient(cc *grpc.ClientConn) MicroClient { + return µClient{cc} +} + +func (c *microClient) Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/go.micro.client.Micro/Call", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *microClient) Stream(ctx context.Context, opts ...grpc.CallOption) (Micro_StreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_Micro_serviceDesc.Streams[0], "/go.micro.client.Micro/Stream", opts...) + if err != nil { + return nil, err + } + x := µStreamClient{stream} + return x, nil +} + +type Micro_StreamClient interface { + Send(*Request) error + Recv() (*Response, error) + grpc.ClientStream +} + +type microStreamClient struct { + grpc.ClientStream +} + +func (x *microStreamClient) Send(m *Request) error { + return x.ClientStream.SendMsg(m) +} + +func (x *microStreamClient) Recv() (*Response, error) { + m := new(Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *microClient) Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) { + out := new(Message) + err := c.cc.Invoke(ctx, "/go.micro.client.Micro/Publish", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MicroServer is the server API for Micro service. +type MicroServer interface { + // Call allows a single request to be made + Call(context.Context, *Request) (*Response, error) + // Stream is a bidirectional stream + Stream(Micro_StreamServer) error + // Publish publishes a message and returns an empty Message + Publish(context.Context, *Message) (*Message, error) +} + +func RegisterMicroServer(s *grpc.Server, srv MicroServer) { + s.RegisterService(&_Micro_serviceDesc, srv) +} + +func _Micro_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.(MicroServer).Call(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.client.Micro/Call", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MicroServer).Call(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _Micro_Stream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MicroServer).Stream(µStreamServer{stream}) +} + +type Micro_StreamServer interface { + Send(*Response) error + Recv() (*Request, error) + grpc.ServerStream +} + +type microStreamServer struct { + grpc.ServerStream +} + +func (x *microStreamServer) Send(m *Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *microStreamServer) Recv() (*Request, error) { + m := new(Request) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Micro_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.(MicroServer).Publish(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.client.Micro/Publish", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MicroServer).Publish(ctx, req.(*Message)) + } + return interceptor(ctx, in, info, handler) +} + +var _Micro_serviceDesc = grpc.ServiceDesc{ + ServiceName: "go.micro.client.Micro", + HandlerType: (*MicroServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Call", + Handler: _Micro_Call_Handler, + }, + { + MethodName: "Publish", + Handler: _Micro_Publish_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Stream", + Handler: _Micro_Stream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "micro/go-micro/client/proto/client.proto", +} diff --git a/client/proto/client.proto b/client/proto/client.proto new file mode 100644 index 00000000..b855fa22 --- /dev/null +++ b/client/proto/client.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package go.micro.client; + +// Micro is the micro client interface +service Micro { + // 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; +} diff --git a/client/rpc_client.go b/client/rpc_client.go index d22e7e42..a1f46dea 100644 --- a/client/rpc_client.go +++ b/client/rpc_client.go @@ -1,40 +1,45 @@ package client import ( - "bytes" "context" "fmt" - "net" "os" - "strconv" "sync" "sync/atomic" "time" "github.com/google/uuid" "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/client/pool" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/codec" "github.com/micro/go-micro/errors" "github.com/micro/go-micro/metadata" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" "github.com/micro/go-micro/transport" + "github.com/micro/go-micro/util/buf" ) type rpcClient struct { once sync.Once opts Options - pool *pool + pool pool.Pool seq uint64 } func newRpcClient(opt ...Option) Client { opts := newOptions(opt...) + p := pool.NewPool( + pool.Size(opts.PoolSize), + pool.TTL(opts.PoolTTL), + pool.Transport(opts.Transport), + ) + rc := &rpcClient{ once: sync.Once{}, opts: opts, - pool: newPool(opts.PoolSize, opts.PoolTTL), + pool: p, seq: 0, } @@ -60,9 +65,6 @@ func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) { func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, resp interface{}, opts CallOptions) error { address := node.Address - if node.Port > 0 { - address = fmt.Sprintf("%s:%d", address, node.Port) - } msg := &transport.Message{ Header: make(map[string]string), @@ -95,13 +97,13 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, } var grr error - c, err := r.pool.getConn(address, r.opts.Transport, transport.WithTimeout(opts.DialTimeout)) + c, err := r.pool.Get(address, transport.WithTimeout(opts.DialTimeout)) if err != nil { return errors.InternalServerError("go.micro.client", "connection error: %v", err) } defer func() { // defer execution of release - r.pool.release(address, c, grr) + r.pool.Release(c, grr) }() seq := atomic.LoadUint64(&r.seq) @@ -160,9 +162,6 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request, opts CallOptions) (Stream, error) { address := node.Address - if node.Port > 0 { - address = fmt.Sprintf("%s:%d", address, node.Port) - } msg := &transport.Message{ Header: make(map[string]string), @@ -253,17 +252,22 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request func (r *rpcClient) Init(opts ...Option) error { size := r.opts.PoolSize ttl := r.opts.PoolTTL + tr := r.opts.Transport for _, o := range opts { o(&r.opts) } // update pool configuration if the options changed - if size != r.opts.PoolSize || ttl != r.opts.PoolTTL { - r.pool.Lock() - r.pool.size = r.opts.PoolSize - r.pool.ttl = int64(r.opts.PoolTTL.Seconds()) - r.pool.Unlock() + if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport { + // close existing pool + r.pool.Close() + // create new pool + r.pool = pool.NewPool( + pool.Size(r.opts.PoolSize), + pool.TTL(r.opts.PoolTTL), + pool.Transport(r.opts.Transport), + ) } return nil @@ -283,29 +287,26 @@ func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, erro // get proxy address if prx := os.Getenv("MICRO_PROXY_ADDRESS"); len(prx) > 0 { - opts.Address = prx + opts.Address = []string{prx} } // return remote address if len(opts.Address) > 0 { - address := opts.Address - port := 0 + nodes := make([]*registry.Node, len(opts.Address)) - host, sport, err := net.SplitHostPort(opts.Address) - if err == nil { - address = host - port, _ = strconv.Atoi(sport) - } - - return func() (*registry.Node, error) { - return ®istry.Node{ + for i, address := range opts.Address { + nodes[i] = ®istry.Node{ Address: address, - Port: port, // Set the protocol Metadata: map[string]string{ "protocol": "mucp", }, - }, nil + } + } + + // crude return method + return func() (*registry.Node, error) { + return nodes[time.Now().Unix()%int64(len(nodes))], nil }, nil } @@ -537,10 +538,13 @@ func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOpt if err != nil { return errors.InternalServerError("go.micro.client", err.Error()) } - b := &buffer{bytes.NewBuffer(nil)} + + // new buffer + b := buf.New(nil) + if err := cf(b).Write(&codec.Message{ Target: topic, - Type: codec.Publication, + Type: codec.Event, Header: map[string]string{ "Micro-Id": id, "Micro-Topic": msg.Topic(), diff --git a/client/rpc_client_test.go b/client/rpc_client_test.go index 219580cd..14547e9f 100644 --- a/client/rpc_client_test.go +++ b/client/rpc_client_test.go @@ -5,10 +5,10 @@ import ( "fmt" "testing" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/errors" "github.com/micro/go-micro/registry" "github.com/micro/go-micro/registry/memory" - "github.com/micro/go-micro/selector" ) func newTestRegistry() registry.Registry { @@ -22,8 +22,7 @@ func TestCallAddress(t *testing.T) { var called bool service := "test.service" endpoint := "Test.Endpoint" - address := "10.1.10.1" - port := 8080 + address := "10.1.10.1:8080" wrap := func(cf CallFunc) CallFunc { return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error { @@ -41,10 +40,6 @@ func TestCallAddress(t *testing.T) { return fmt.Errorf("expected address: %s got %s", address, node.Address) } - if node.Port != port { - return fmt.Errorf("expected address: %d got %d", port, node.Port) - } - // don't do the call return nil } @@ -60,7 +55,7 @@ func TestCallAddress(t *testing.T) { req := c.NewRequest(service, endpoint, nil) // test calling remote address - if err := c.Call(context.Background(), req, nil, WithAddress(fmt.Sprintf("%s:%d", address, port))); err != nil { + if err := c.Call(context.Background(), req, nil, WithAddress(address)); err != nil { t.Fatal("call with address error", err) } @@ -114,8 +109,7 @@ func TestCallWrapper(t *testing.T) { id := "test.1" service := "test.service" endpoint := "Test.Endpoint" - address := "10.1.10.1" - port := 8080 + address := "10.1.10.1:8080" wrap := func(cf CallFunc) CallFunc { return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error { @@ -152,7 +146,6 @@ func TestCallWrapper(t *testing.T) { ®istry.Node{ Id: id, Address: address, - Port: port, }, }, }) diff --git a/client/rpc_pool.go b/client/rpc_pool.go deleted file mode 100644 index 9fdd7736..00000000 --- a/client/rpc_pool.go +++ /dev/null @@ -1,87 +0,0 @@ -package client - -import ( - "sync" - "time" - - "github.com/micro/go-micro/transport" -) - -type pool struct { - size int - ttl int64 - - sync.Mutex - conns map[string][]*poolConn -} - -type poolConn struct { - transport.Client - created int64 -} - -func newPool(size int, ttl time.Duration) *pool { - return &pool{ - size: size, - ttl: int64(ttl.Seconds()), - conns: make(map[string][]*poolConn), - } -} - -// NoOp the Close since we manage it -func (p *poolConn) Close() error { - return nil -} - -func (p *pool) getConn(addr string, tr transport.Transport, opts ...transport.DialOption) (*poolConn, error) { - p.Lock() - conns := p.conns[addr] - now := time.Now().Unix() - - // while we have conns check age and then return one - // otherwise we'll create a new conn - for len(conns) > 0 { - conn := conns[len(conns)-1] - conns = conns[:len(conns)-1] - p.conns[addr] = conns - - // if conn is old kill it and move on - if d := now - conn.created; d > p.ttl { - conn.Client.Close() - continue - } - - // we got a good conn, lets unlock and return it - p.Unlock() - - return conn, nil - } - - p.Unlock() - - // create new conn - c, err := tr.Dial(addr, opts...) - if err != nil { - return nil, err - } - return &poolConn{c, time.Now().Unix()}, nil -} - -func (p *pool) release(addr string, conn *poolConn, err error) { - // don't store the conn if it has errored - if err != nil { - conn.Client.Close() - return - } - - // otherwise put it back for reuse - p.Lock() - conns := p.conns[addr] - if len(conns) >= p.size { - p.Unlock() - conn.Client.Close() - return - } - p.conns[addr] = append(conns, conn) - p.Unlock() -} diff --git a/selector/common_test.go b/client/selector/common_test.go similarity index 76% rename from selector/common_test.go rename to client/selector/common_test.go index aa8c15c7..7aba0542 100644 --- a/selector/common_test.go +++ b/client/selector/common_test.go @@ -14,13 +14,11 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.0-123", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, { Id: "foo-1.0.0-321", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, }, }, @@ -30,8 +28,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.1-321", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, @@ -41,8 +38,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.3-345", - Address: "localhost", - Port: 8888, + Address: "localhost:8888", }, }, }, diff --git a/selector/default.go b/client/selector/default.go similarity index 100% rename from selector/default.go rename to client/selector/default.go diff --git a/selector/default_test.go b/client/selector/default_test.go similarity index 100% rename from selector/default_test.go rename to client/selector/default_test.go diff --git a/selector/dns/dns.go b/client/selector/dns/dns.go similarity index 95% rename from selector/dns/dns.go rename to client/selector/dns/dns.go index d3882ac1..df6c209f 100644 --- a/selector/dns/dns.go +++ b/client/selector/dns/dns.go @@ -2,11 +2,12 @@ package dns import ( + "fmt" "net" "strconv" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" ) type dnsSelector struct { @@ -66,8 +67,7 @@ func (d *dnsSelector) Select(service string, opts ...selector.SelectOption) (sel for _, node := range srv { nodes = append(nodes, ®istry.Node{ Id: node.Target, - Address: node.Target, - Port: int(node.Port), + Address: fmt.Sprintf("%s:%d", node.Target, node.Port), }) } diff --git a/selector/filter.go b/client/selector/filter.go similarity index 100% rename from selector/filter.go rename to client/selector/filter.go diff --git a/selector/filter_test.go b/client/selector/filter_test.go similarity index 100% rename from selector/filter_test.go rename to client/selector/filter_test.go diff --git a/selector/options.go b/client/selector/options.go similarity index 100% rename from selector/options.go rename to client/selector/options.go diff --git a/selector/registry/options.go b/client/selector/registry/options.go similarity index 86% rename from selector/registry/options.go rename to client/selector/registry/options.go index 90aaf802..007c5023 100644 --- a/selector/registry/options.go +++ b/client/selector/registry/options.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/client/selector" ) // Set the registry cache ttl diff --git a/selector/registry/registry.go b/client/selector/registry/registry.go similarity index 84% rename from selector/registry/registry.go rename to client/selector/registry/registry.go index b6d8600d..fe8373a7 100644 --- a/selector/registry/registry.go +++ b/client/selector/registry/registry.go @@ -2,7 +2,7 @@ package registry import ( - "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/client/selector" ) // NewSelector returns a new registry selector diff --git a/client/selector/router/router.go b/client/selector/router/router.go new file mode 100644 index 00000000..90f74a9a --- /dev/null +++ b/client/selector/router/router.go @@ -0,0 +1,272 @@ +// Package router is a network/router selector +package router + +import ( + "context" + "os" + "sort" + "sync" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/client/selector" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/router" + pb "github.com/micro/go-micro/router/proto" +) + +type routerSelector struct { + opts selector.Options + + // the router + r router.Router + + // the client we have + c client.Client + + // the client for the remote router + rs pb.RouterService + + // name of the router + name string + + // address of the remote router + addr string + + // whether to use the remote router + remote bool +} + +type clientKey struct{} +type routerKey struct{} + +// getRoutes returns the routes whether they are remote or local +func (r *routerSelector) getRoutes(service string) ([]router.Route, error) { + if !r.remote { + // lookup router for routes for the service + return r.r.Lookup(router.NewQuery( + router.QueryService(service), + )) + } + + // lookup the remote router + + var addrs []string + + // set the remote address if specified + if len(r.addr) > 0 { + addrs = append(addrs, r.addr) + } else { + // we have a name so we need to check the registry + services, err := r.c.Options().Registry.GetService(r.name) + if err != nil { + return nil, err + } + + for _, service := range services { + for _, node := range service.Nodes { + addrs = append(addrs, node.Address) + } + } + } + + // no router addresses available + if len(addrs) == 0 { + return nil, selector.ErrNoneAvailable + } + + var pbRoutes *pb.LookupResponse + var err error + + // TODO: implement backoff and retries + for _, addr := range addrs { + // call the router + pbRoutes, err = r.rs.Lookup(context.Background(), &pb.LookupRequest{ + Query: &pb.Query{ + Service: service, + }, + }, client.WithAddress(addr)) + if err != nil { + continue + } + break + } + + // errored out + if err != nil { + return nil, err + } + + // no routes + if pbRoutes == nil { + return nil, selector.ErrNoneAvailable + } + + var routes []router.Route + + // convert from pb to []*router.Route + for _, r := range pbRoutes.Routes { + routes = append(routes, router.Route{ + Service: r.Service, + Address: r.Address, + Gateway: r.Gateway, + Network: r.Network, + Link: r.Link, + Metric: int(r.Metric), + }) + } + + return routes, nil +} + +func (r *routerSelector) Init(opts ...selector.Option) error { + // no op + return nil +} + +func (r *routerSelector) Options() selector.Options { + return r.opts +} + +func (r *routerSelector) Select(service string, opts ...selector.SelectOption) (selector.Next, error) { + // TODO: pull routes asynchronously and cache + routes, err := r.getRoutes(service) + if err != nil { + return nil, err + } + + // no routes return not found error + if len(routes) == 0 { + return nil, selector.ErrNotFound + } + + // TODO: apply filters by pseudo constructing service + + // sort the routes based on metric + sort.Slice(routes, func(i, j int) bool { + return routes[i].Metric < routes[j].Metric + }) + + // roundrobin assuming routes are in metric preference order + var i int + var mtx sync.Mutex + + return func() (*registry.Node, error) { + // get index and increment counter with every call to next + mtx.Lock() + idx := i + i++ + mtx.Unlock() + + // get route based on idx + route := routes[idx%len(routes)] + + // defaults to gateway and no port + address := route.Address + if len(route.Gateway) > 0 { + address = route.Gateway + } + + // return as a node + return ®istry.Node{ + // TODO: add id and metadata if we can + Address: address, + }, nil + }, nil +} + +func (r *routerSelector) Mark(service string, node *registry.Node, err error) { + // TODO: pass back metrics or information to the router + return +} + +func (r *routerSelector) Reset(service string) { + // TODO: reset the metrics or information at the router + return +} + +func (r *routerSelector) Close() error { + // stop the router advertisements + return r.r.Stop() +} + +func (r *routerSelector) String() string { + return "router" +} + +// NewSelector returns a new router based selector +func NewSelector(opts ...selector.Option) selector.Selector { + options := selector.Options{ + Context: context.Background(), + } + + for _, o := range opts { + o(&options) + } + + // set default registry if not set + if options.Registry == nil { + options.Registry = registry.DefaultRegistry + } + + // try get router from the context + r, ok := options.Context.Value(routerKey{}).(router.Router) + if !ok { + // TODO: Use router.DefaultRouter? + r = router.NewRouter( + router.Registry(options.Registry), + ) + } + + // try get client from the context + c, ok := options.Context.Value(clientKey{}).(client.Client) + if !ok { + c = client.DefaultClient + } + + // get the router from env vars if its a remote service + remote := true + routerName := os.Getenv("MICRO_ROUTER") + routerAddress := os.Getenv("MICRO_ROUTER_ADDRESS") + + // start the router advertisements if we're running it locally + if len(routerName) == 0 && len(routerAddress) == 0 { + go r.Advertise() + remote = false + } + + return &routerSelector{ + opts: options, + // set the internal router + r: r, + // set the client + c: c, + // set the router client + rs: pb.NewRouterService(routerName, c), + // name of the router + name: routerName, + // address of router + addr: routerAddress, + // let ourselves know to use the remote router + remote: remote, + } +} + +// WithClient sets the client for the request +func WithClient(c client.Client) selector.Option { + return func(o *selector.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, clientKey{}, c) + } +} + +// WithRouter sets the router as an option +func WithRouter(r router.Router) selector.Option { + return func(o *selector.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, routerKey{}, r) + } +} diff --git a/selector/selector.go b/client/selector/selector.go similarity index 100% rename from selector/selector.go rename to client/selector/selector.go diff --git a/selector/static/static.go b/client/selector/static/static.go similarity index 82% rename from selector/static/static.go rename to client/selector/static/static.go index 11c2c9cf..9bd18f03 100644 --- a/selector/static/static.go +++ b/client/selector/static/static.go @@ -2,11 +2,8 @@ package static import ( - "net" - "strconv" - + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" ) // staticSelector is a static selector @@ -26,20 +23,10 @@ func (s *staticSelector) Options() selector.Options { } func (s *staticSelector) Select(service string, opts ...selector.SelectOption) (selector.Next, error) { - var port int - addr, pt, err := net.SplitHostPort(service) - if err != nil { - addr = service - port = 0 - } else { - port, _ = strconv.Atoi(pt) - } - return func() (*registry.Node, error) { return ®istry.Node{ Id: service, - Address: addr, - Port: port, + Address: service, }, nil }, nil } diff --git a/selector/strategy.go b/client/selector/strategy.go similarity index 100% rename from selector/strategy.go rename to client/selector/strategy.go diff --git a/selector/strategy_test.go b/client/selector/strategy_test.go similarity index 82% rename from selector/strategy_test.go rename to client/selector/strategy_test.go index 17090e73..8ea9376a 100644 --- a/selector/strategy_test.go +++ b/client/selector/strategy_test.go @@ -14,13 +14,11 @@ func TestStrategies(t *testing.T) { Nodes: []*registry.Node{ ®istry.Node{ Id: "test1-1", - Address: "10.0.0.1", - Port: 1001, + Address: "10.0.0.1:1001", }, ®istry.Node{ Id: "test1-2", - Address: "10.0.0.2", - Port: 1002, + Address: "10.0.0.2:1002", }, }, }, @@ -30,13 +28,11 @@ func TestStrategies(t *testing.T) { Nodes: []*registry.Node{ ®istry.Node{ Id: "test1-3", - Address: "10.0.0.3", - Port: 1003, + Address: "10.0.0.3:1003", }, ®istry.Node{ Id: "test1-4", - Address: "10.0.0.4", - Port: 1004, + Address: "10.0.0.4:1004", }, }, }, diff --git a/codec/codec.go b/codec/codec.go index 092caa05..b4feb0a4 100644 --- a/codec/codec.go +++ b/codec/codec.go @@ -9,7 +9,7 @@ const ( Error MessageType = iota Request Response - Publication + Event ) type MessageType int diff --git a/codec/jsonrpc/jsonrpc.go b/codec/jsonrpc/jsonrpc.go index 8f0c2c4f..f98b2842 100644 --- a/codec/jsonrpc/jsonrpc.go +++ b/codec/jsonrpc/jsonrpc.go @@ -33,7 +33,7 @@ func (j *jsonCodec) Write(m *codec.Message, b interface{}) error { return j.c.Write(m, b) case codec.Response, codec.Error: return j.s.Write(m, b) - case codec.Publication: + case codec.Event: data, err := json.Marshal(b) if err != nil { return err @@ -54,7 +54,7 @@ func (j *jsonCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error { return j.s.ReadHeader(m) case codec.Response: return j.c.ReadHeader(m) - case codec.Publication: + case codec.Event: _, err := io.Copy(j.buf, j.rwc) return err default: @@ -69,7 +69,7 @@ func (j *jsonCodec) ReadBody(b interface{}) error { return j.s.ReadBody(b) case codec.Response: return j.c.ReadBody(b) - case codec.Publication: + case codec.Event: if b != nil { return json.Unmarshal(j.buf.Bytes(), b) } diff --git a/codec/protorpc/protorpc.go b/codec/protorpc/protorpc.go index 4732b98e..255b93a4 100644 --- a/codec/protorpc/protorpc.go +++ b/codec/protorpc/protorpc.go @@ -99,7 +99,7 @@ func (c *protoCodec) Write(m *codec.Message, b interface{}) error { return err } } - case codec.Publication: + case codec.Event: data, err := proto.Marshal(b.(proto.Message)) if err != nil { return err @@ -141,7 +141,7 @@ func (c *protoCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error { m.Method = rtmp.GetServiceMethod() m.Id = fmt.Sprintf("%d", rtmp.GetSeq()) m.Error = rtmp.GetError() - case codec.Publication: + case codec.Event: _, err := io.Copy(c.buf, c.rwc) return err default: @@ -159,7 +159,7 @@ func (c *protoCodec) ReadBody(b interface{}) error { if err != nil { return err } - case codec.Publication: + case codec.Event: data = c.buf.Bytes() default: return fmt.Errorf("Unrecognised message type: %v", c.mt) diff --git a/common_test.go b/common_test.go index 612cdb37..d4fd4bfb 100644 --- a/common_test.go +++ b/common_test.go @@ -14,13 +14,11 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.0-123", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, { Id: "foo-1.0.0-321", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, }, }, @@ -30,8 +28,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.1-321", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, @@ -41,8 +38,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.3-345", - Address: "localhost", - Port: 8888, + Address: "localhost:8888", }, }, }, diff --git a/config/README.md b/config/README.md index f1265f51..56e21db9 100644 --- a/config/README.md +++ b/config/README.md @@ -1,6 +1,6 @@ # Config [![GoDoc](https://godoc.org/github.com/micro/go-micro/config?status.svg)](https://godoc.org/github.com/micro/go-micro/config) -Go Config is a pluggable dynamic config library. +Config is a pluggable dynamic config package Most config in applications are statically configured or include complex logic to load from multiple sources. Go Config makes this easy, pluggable and mergeable. You'll never have to deal with config in the same way again. diff --git a/cmd/cmd.go b/config/cmd/cmd.go similarity index 97% rename from cmd/cmd.go rename to config/cmd/cmd.go index 310aa431..beb4ba97 100644 --- a/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -14,8 +14,8 @@ import ( cgrpc "github.com/micro/go-micro/client/grpc" cmucp "github.com/micro/go-micro/client/mucp" "github.com/micro/go-micro/server" - smucp "github.com/micro/go-micro/server/mucp" sgrpc "github.com/micro/go-micro/server/grpc" + smucp "github.com/micro/go-micro/server/mucp" "github.com/micro/go-micro/util/log" // brokers @@ -32,15 +32,17 @@ import ( rmem "github.com/micro/go-micro/registry/memory" // selectors - "github.com/micro/go-micro/selector" - "github.com/micro/go-micro/selector/dns" - "github.com/micro/go-micro/selector/static" + "github.com/micro/go-micro/client/selector" + "github.com/micro/go-micro/client/selector/dns" + "github.com/micro/go-micro/client/selector/router" + "github.com/micro/go-micro/client/selector/static" // transports "github.com/micro/go-micro/transport" tgrpc "github.com/micro/go-micro/transport/grpc" thttp "github.com/micro/go-micro/transport/http" tmem "github.com/micro/go-micro/transport/memory" + "github.com/micro/go-micro/transport/quic" ) type Cmd interface { @@ -180,7 +182,7 @@ var ( } DefaultClients = map[string]func(...client.Option) client.Client{ - "rpc": client.NewClient, + "rpc": client.NewClient, "mucp": cmucp.NewClient, "grpc": cgrpc.NewClient, } @@ -196,11 +198,12 @@ var ( "default": selector.NewSelector, "dns": dns.NewSelector, "cache": selector.NewSelector, + "router": router.NewSelector, "static": static.NewSelector, } DefaultServers = map[string]func(...server.Option) server.Server{ - "rpc": server.NewServer, + "rpc": server.NewServer, "mucp": smucp.NewServer, "grpc": sgrpc.NewServer, } @@ -209,6 +212,7 @@ var ( "memory": tmem.NewTransport, "http": thttp.NewTransport, "grpc": tgrpc.NewTransport, + "quic": quic.NewTransport, } // used for default selection as the fall back diff --git a/cmd/options.go b/config/cmd/options.go similarity index 98% rename from cmd/options.go rename to config/cmd/options.go index 1dcd4755..1f221f35 100644 --- a/cmd/options.go +++ b/config/cmd/options.go @@ -5,8 +5,8 @@ import ( "github.com/micro/go-micro/broker" "github.com/micro/go-micro/client" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" "github.com/micro/go-micro/server" "github.com/micro/go-micro/transport" ) diff --git a/config/default_test.go b/config/default_test.go index 9d1bd03e..8a6adc73 100644 --- a/config/default_test.go +++ b/config/default_test.go @@ -8,9 +8,25 @@ import ( "testing" "time" + "github.com/micro/go-micro/config/source/env" "github.com/micro/go-micro/config/source/file" ) +func createFileForIssue18(t *testing.T, content string) *os.File { + data := []byte(content) + path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano())) + fh, err := os.Create(path) + if err != nil { + t.Error(err) + } + _, err = fh.Write(data) + if err != nil { + t.Error(err) + } + + return fh +} + func createFileForTest(t *testing.T) *os.File { data := []byte(`{"foo": "bar"}`) path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano())) @@ -26,7 +42,7 @@ func createFileForTest(t *testing.T) *os.File { return fh } -func TestLoadWithGoodFile(t *testing.T) { +func TestConfigLoadWithGoodFile(t *testing.T) { fh := createFileForTest(t) path := fh.Name() defer func() { @@ -44,7 +60,7 @@ func TestLoadWithGoodFile(t *testing.T) { } } -func TestLoadWithInvalidFile(t *testing.T) { +func TestConfigLoadWithInvalidFile(t *testing.T) { fh := createFileForTest(t) path := fh.Name() defer func() { @@ -68,34 +84,35 @@ func TestLoadWithInvalidFile(t *testing.T) { } } -func TestConsul(t *testing.T) { - /*consulSource := consul.NewSource( - // optionally specify consul address; default to localhost:8500 - consul.WithAddress("131.150.38.111:8500"), - // optionally specify prefix; defaults to /micro/config - consul.WithPrefix("/project"), - // optionally strip the provided prefix from the keys, defaults to false - consul.StripPrefix(true), - consul.WithDatacenter("dc1"), - consul.WithToken("xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"), +func TestConfigMerge(t *testing.T) { + fh := createFileForIssue18(t, `{ + "amqp": { + "host": "rabbit.platform", + "port": 80 + }, + "handler": { + "exchange": "springCloudBus" + } +}`) + path := fh.Name() + defer func() { + fh.Close() + os.Remove(path) + }() + os.Setenv("AMQP_HOST", "rabbit.testing.com") + + conf := NewConfig() + conf.Load( + file.NewSource( + file.WithPath(path), + ), + env.NewSource(), ) - // Create new config - conf := NewConfig() - - // Load file source - err := conf.Load(consulSource) - if err != nil { - t.Error(err) - return + actualHost := conf.Get("amqp", "host").String("backup") + if actualHost != "rabbit.testing.com" { + t.Fatalf("Expected %v but got %v", + "rabbit.testing.com", + actualHost) } - - m := conf.Map() - t.Log("m: ", m) - - v := conf.Get("project", "dc111", "port") - - t.Log("v: ", v.Int(13))*/ - - t.Log("OK") } diff --git a/config/issue18_test.go b/config/issue18_test.go deleted file mode 100644 index 5fed22bf..00000000 --- a/config/issue18_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package config - -import ( - "fmt" - "os" - "path/filepath" - "testing" - "time" - - "github.com/micro/go-micro/config/source/env" - "github.com/micro/go-micro/config/source/file" -) - -func createFileForIssue18(t *testing.T, content string) *os.File { - data := []byte(content) - path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano())) - fh, err := os.Create(path) - if err != nil { - t.Error(err) - } - _, err = fh.Write(data) - if err != nil { - t.Error(err) - } - - return fh -} - -func TestIssue18(t *testing.T) { - fh := createFileForIssue18(t, `{ - "amqp": { - "host": "rabbit.platform", - "port": 80 - }, - "handler": { - "exchange": "springCloudBus" - } -}`) - path := fh.Name() - defer func() { - fh.Close() - os.Remove(path) - }() - os.Setenv("AMQP_HOST", "rabbit.testing.com") - - conf := NewConfig() - conf.Load( - file.NewSource( - file.WithPath(path), - ), - env.NewSource(), - ) - - actualHost := conf.Get("amqp", "host").String("backup") - if actualHost != "rabbit.testing.com" { - t.Fatalf("Expected %v but got %v", - "rabbit.testing.com", - actualHost) - } -} diff --git a/config/reader/json/values_test.go b/config/reader/json/values_test.go index 516199a6..97aec68a 100644 --- a/config/reader/json/values_test.go +++ b/config/reader/json/values_test.go @@ -1,39 +1,85 @@ package json import ( + "reflect" "testing" "github.com/micro/go-micro/config/source" ) func TestValues(t *testing.T) { - data := []byte(`{"foo": "bar", "baz": {"bar": "cat"}}`) - + emptyStr := "" testData := []struct { - path []string - value string + csdata []byte + path []string + accepter interface{} + value interface{} }{ { + []byte(`{"foo": "bar", "baz": {"bar": "cat"}}`), []string{"foo"}, + emptyStr, "bar", }, { + []byte(`{"foo": "bar", "baz": {"bar": "cat"}}`), []string{"baz", "bar"}, + emptyStr, "cat", }, } - values, err := newValues(&source.ChangeSet{ - Data: data, - }) + for idx, test := range testData { + values, err := newValues(&source.ChangeSet{ + Data: test.csdata, + }) + if err != nil { + t.Fatal(err) + } - if err != nil { - t.Fatal(err) - } - - for _, test := range testData { - if v := values.Get(test.path...).String(""); v != test.value { - t.Fatalf("Expected %s got %s for path %v", test.value, v, test.path) + err = values.Get(test.path...).Scan(&test.accepter) + if err != nil { + t.Fatal(err) + } + if test.accepter != test.value { + t.Fatalf("No.%d Expected %v got %v for path %v", idx, test.value, test.accepter, test.path) + } + } +} + +func TestStructArray(t *testing.T) { + type T struct { + Foo string + } + + emptyTSlice := []T{} + + testData := []struct { + csdata []byte + accepter []T + value []T + }{ + { + []byte(`[{"foo": "bar"}]`), + emptyTSlice, + []T{T{Foo: "bar"}}, + }, + } + + for idx, test := range testData { + values, err := newValues(&source.ChangeSet{ + Data: test.csdata, + }) + if err != nil { + t.Fatal(err) + } + + err = values.Get().Scan(&test.accepter) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(test.accepter, test.value) { + t.Fatalf("No.%d Expected %v got %v", idx, test.value, test.accepter) } } } diff --git a/config/source/cli/cli.go b/config/source/cli/cli.go index 86b27f10..362ea79f 100644 --- a/config/source/cli/cli.go +++ b/config/source/cli/cli.go @@ -9,7 +9,7 @@ import ( "github.com/imdario/mergo" "github.com/micro/cli" - "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/config/cmd" "github.com/micro/go-micro/config/source" ) diff --git a/config/source/cli/cli_test.go b/config/source/cli/cli_test.go index ada1a0d6..8e1b743c 100644 --- a/config/source/cli/cli_test.go +++ b/config/source/cli/cli_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/micro/cli" - "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/config/cmd" "github.com/micro/go-micro/config/source" ) diff --git a/config/source/consul/consul.go b/config/source/consul/consul.go index 16e85332..f5c3c695 100644 --- a/config/source/consul/consul.go +++ b/config/source/consul/consul.go @@ -74,6 +74,11 @@ func NewSource(opts ...source.Option) source.Source { // use default config config := api.DefaultConfig() + // use the consul config passed in the options if any + if co, ok := options.Context.Value(configKey{}).(*api.Config); ok { + config = co + } + // check if there are any addrs a, ok := options.Context.Value(addressKey{}).(string) if ok { diff --git a/config/source/consul/options.go b/config/source/consul/options.go index 9420a803..8bca6a66 100644 --- a/config/source/consul/options.go +++ b/config/source/consul/options.go @@ -3,6 +3,7 @@ package consul import ( "context" + "github.com/hashicorp/consul/api" "github.com/micro/go-micro/config/source" ) @@ -11,6 +12,7 @@ type prefixKey struct{} type stripPrefixKey struct{} type dcKey struct{} type tokenKey struct{} +type configKey struct{} // WithAddress sets the consul address func WithAddress(a string) source.Option { @@ -61,3 +63,13 @@ func WithToken(p string) source.Option { o.Context = context.WithValue(o.Context, tokenKey{}, p) } } + +// WithConfig set consul-specific options +func WithConfig(c *api.Config) source.Option { + return func(o *source.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, configKey{}, c) + } +} diff --git a/config/source/consul/util.go b/config/source/consul/util.go index db6f708d..1deacbb4 100644 --- a/config/source/consul/util.go +++ b/config/source/consul/util.go @@ -8,40 +8,80 @@ import ( "github.com/micro/go-micro/config/encoder" ) +type configValue interface { + Value() interface{} + Decode(encoder.Encoder, []byte) error +} +type configArrayValue struct { + v []interface{} +} + +func (a *configArrayValue) Value() interface{} { return a.v } +func (a *configArrayValue) Decode(e encoder.Encoder, b []byte) error { + return e.Decode(b, &a.v) +} + +type configMapValue struct { + v map[string]interface{} +} + +func (m *configMapValue) Value() interface{} { return m.v } +func (m *configMapValue) Decode(e encoder.Encoder, b []byte) error { + return e.Decode(b, &m.v) +} + func makeMap(e encoder.Encoder, kv api.KVPairs, stripPrefix string) (map[string]interface{}, error) { + data := make(map[string]interface{}) // consul guarantees lexicographic order, so no need to sort for _, v := range kv { - pathString := strings.TrimPrefix(strings.TrimPrefix(v.Key, stripPrefix), "/") - var val map[string]interface{} + pathString := strings.TrimPrefix(strings.TrimPrefix(v.Key, strings.TrimPrefix(stripPrefix, "/")), "/") + if pathString == "" { + continue + } + var val configValue + var err error // ensure a valid value is stored at this location if len(v.Value) > 0 { - if err := e.Decode(v.Value, &val); err != nil { + // try to decode into map value or array value + arrayV := &configArrayValue{v: []interface{}{}} + mapV := &configMapValue{v: map[string]interface{}{}} + switch { + case arrayV.Decode(e, v.Value) == nil: + val = arrayV + case mapV.Decode(e, v.Value) == nil: + val = mapV + default: return nil, fmt.Errorf("faild decode value. path: %s, error: %s", pathString, err) } } // set target at the root target := data - - // then descend to the target location, creating as we go, if need be - if pathString != "" { - path := strings.Split(pathString, "/") - // find (or create) the location we want to put this value at - for _, dir := range path { - if _, ok := target[dir]; !ok { - target[dir] = make(map[string]interface{}) - } - target = target[dir].(map[string]interface{}) + path := strings.Split(pathString, "/") + // find (or create) the leaf node we want to put this value at + for _, dir := range path[:len(path)-1] { + if _, ok := target[dir]; !ok { + target[dir] = make(map[string]interface{}) } - + target = target[dir].(map[string]interface{}) } + leafDir := path[len(path)-1] + // copy over the keys from the value - for k := range val { - target[k] = val[k] + switch val.(type) { + case *configArrayValue: + target[leafDir] = val.Value() + case *configMapValue: + target[leafDir] = make(map[string]interface{}) + target = target[leafDir].(map[string]interface{}) + mapv := val.Value().(map[string]interface{}) + for k := range mapv { + target[k] = mapv[k] + } } } diff --git a/config/source/memory/README.md b/config/source/memory/README.md index 2c8038d8..7f1a4008 100644 --- a/config/source/memory/README.md +++ b/config/source/memory/README.md @@ -27,7 +27,7 @@ Specify source with data ```go memorySource := memory.NewSource( - memory.WithData(data), + memory.WithJSON(data), ) ``` diff --git a/data/data.go b/data/data.go new file mode 100644 index 00000000..eb6157d2 --- /dev/null +++ b/data/data.go @@ -0,0 +1,2 @@ +// Package data is an interface for data access +package data diff --git a/store/consul/consul.go b/data/store/consul/consul.go similarity index 97% rename from store/consul/consul.go rename to data/store/consul/consul.go index fe937e6c..174d043c 100644 --- a/store/consul/consul.go +++ b/data/store/consul/consul.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/consul/api" "github.com/micro/go-micro/config/options" - "github.com/micro/go-micro/store" + "github.com/micro/go-micro/data/store" ) type ckv struct { diff --git a/store/memory/memory.go b/data/store/memory/memory.go similarity index 81% rename from store/memory/memory.go rename to data/store/memory/memory.go index d6b23e20..bb7892e8 100644 --- a/store/memory/memory.go +++ b/data/store/memory/memory.go @@ -6,7 +6,7 @@ import ( "time" "github.com/micro/go-micro/config/options" - "github.com/micro/go-micro/store" + "github.com/micro/go-micro/data/store" ) type memoryStore struct { @@ -32,10 +32,16 @@ func (m *memoryStore) Dump() ([]*store.Record, error) { d := v.r.Expiry t := time.Since(v.c) - // expired - if d > time.Duration(0) && t > d { - continue + if d > time.Duration(0) { + // expired + if t > d { + continue + } + // update expiry + v.r.Expiry -= t + v.c = time.Now() } + values = append(values, v.r) } @@ -56,8 +62,13 @@ func (m *memoryStore) Read(key string) (*store.Record, error) { t := time.Since(v.c) // expired - if d > time.Duration(0) && t > d { - return nil, store.ErrNotFound + if d > time.Duration(0) { + if t > d { + return nil, store.ErrNotFound + } + // update expiry + v.r.Expiry -= t + v.c = time.Now() } return v.r, nil diff --git a/data/store/memory/memory_test.go b/data/store/memory/memory_test.go new file mode 100644 index 00000000..44483869 --- /dev/null +++ b/data/store/memory/memory_test.go @@ -0,0 +1,37 @@ +package memory + +import ( + "testing" + "time" + + "github.com/micro/go-micro/data/store" +) + +func TestReadRecordExpire(t *testing.T) { + s := NewStore() + + var ( + key = "foo" + expire = 100 * time.Millisecond + ) + rec := &store.Record{ + Key: key, + Value: nil, + Expiry: expire, + } + s.Write(rec) + + rrec, err := s.Read(key) + if err != nil { + t.Fatal(err) + } + if rrec.Expiry >= expire { + t.Fatal("expiry of read record is not changed") + } + + time.Sleep(expire) + + if _, err := s.Read(key); err != store.ErrNotFound { + t.Fatal("expire elapsed, but key still accessable") + } +} diff --git a/store/options.go b/data/store/options.go similarity index 100% rename from store/options.go rename to data/store/options.go diff --git a/store/store.go b/data/store/store.go similarity index 100% rename from store/store.go rename to data/store/store.go diff --git a/debug/handler/debug.go b/debug/handler/debug.go new file mode 100644 index 00000000..973a6fc4 --- /dev/null +++ b/debug/handler/debug.go @@ -0,0 +1,41 @@ +package handler + +import ( + "context" + "runtime" + "time" + + proto "github.com/micro/go-micro/debug/proto" +) + +type Debug struct { + proto.DebugHandler + started int64 +} + +var ( + DefaultHandler = newDebug() +) + +func newDebug() *Debug { + return &Debug{ + started: time.Now().Unix(), + } +} + +func (d *Debug) Health(ctx context.Context, req *proto.HealthRequest, rsp *proto.HealthResponse) error { + rsp.Status = "ok" + return nil +} + +func (d *Debug) Stats(ctx context.Context, req *proto.StatsRequest, rsp *proto.StatsResponse) error { + var mstat runtime.MemStats + runtime.ReadMemStats(&mstat) + + rsp.Started = uint64(d.started) + rsp.Uptime = uint64(time.Now().Unix() - d.started) + rsp.Memory = mstat.Alloc + rsp.Gc = mstat.PauseTotalNs + rsp.Threads = uint64(runtime.NumGoroutine()) + return nil +} diff --git a/debug/proto/debug.micro.go b/debug/proto/debug.micro.go new file mode 100644 index 00000000..baa06cee --- /dev/null +++ b/debug/proto/debug.micro.go @@ -0,0 +1,108 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: micro/go-micro/debug/proto/debug.proto + +package debug + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +import ( + context "context" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/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 _ context.Context +var _ client.Option +var _ server.Option + +// Client API for Debug service + +type DebugService interface { + Health(ctx context.Context, in *HealthRequest, opts ...client.CallOption) (*HealthResponse, error) + Stats(ctx context.Context, in *StatsRequest, opts ...client.CallOption) (*StatsResponse, error) +} + +type debugService struct { + c client.Client + name string +} + +func NewDebugService(name string, c client.Client) DebugService { + if c == nil { + c = client.NewClient() + } + if len(name) == 0 { + name = "debug" + } + return &debugService{ + c: c, + name: name, + } +} + +func (c *debugService) Health(ctx context.Context, in *HealthRequest, opts ...client.CallOption) (*HealthResponse, error) { + req := c.c.NewRequest(c.name, "Debug.Health", in) + out := new(HealthResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *debugService) Stats(ctx context.Context, in *StatsRequest, opts ...client.CallOption) (*StatsResponse, error) { + req := c.c.NewRequest(c.name, "Debug.Stats", in) + out := new(StatsResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Debug service + +type DebugHandler interface { + Health(context.Context, *HealthRequest, *HealthResponse) error + Stats(context.Context, *StatsRequest, *StatsResponse) error +} + +func RegisterDebugHandler(s server.Server, hdlr DebugHandler, opts ...server.HandlerOption) error { + type debug interface { + Health(ctx context.Context, in *HealthRequest, out *HealthResponse) error + Stats(ctx context.Context, in *StatsRequest, out *StatsResponse) error + } + type Debug struct { + debug + } + h := &debugHandler{hdlr} + return s.Handle(s.NewHandler(&Debug{h}, opts...)) +} + +type debugHandler struct { + DebugHandler +} + +func (h *debugHandler) Health(ctx context.Context, in *HealthRequest, out *HealthResponse) error { + return h.DebugHandler.Health(ctx, in, out) +} + +func (h *debugHandler) Stats(ctx context.Context, in *StatsRequest, out *StatsResponse) error { + return h.DebugHandler.Stats(ctx, in, out) +} diff --git a/debug/proto/debug.pb.go b/debug/proto/debug.pb.go new file mode 100644 index 00000000..5ba4f8c9 --- /dev/null +++ b/debug/proto/debug.pb.go @@ -0,0 +1,336 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: micro/go-micro/debug/proto/debug.proto + +package debug + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + 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 HealthRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HealthRequest) Reset() { *m = HealthRequest{} } +func (m *HealthRequest) String() string { return proto.CompactTextString(m) } +func (*HealthRequest) ProtoMessage() {} +func (*HealthRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_f25415e61bccfa1f, []int{0} +} + +func (m *HealthRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HealthRequest.Unmarshal(m, b) +} +func (m *HealthRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HealthRequest.Marshal(b, m, deterministic) +} +func (m *HealthRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_HealthRequest.Merge(m, src) +} +func (m *HealthRequest) XXX_Size() int { + return xxx_messageInfo_HealthRequest.Size(m) +} +func (m *HealthRequest) XXX_DiscardUnknown() { + xxx_messageInfo_HealthRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_HealthRequest proto.InternalMessageInfo + +type HealthResponse struct { + // default: ok + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HealthResponse) Reset() { *m = HealthResponse{} } +func (m *HealthResponse) String() string { return proto.CompactTextString(m) } +func (*HealthResponse) ProtoMessage() {} +func (*HealthResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f25415e61bccfa1f, []int{1} +} + +func (m *HealthResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HealthResponse.Unmarshal(m, b) +} +func (m *HealthResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HealthResponse.Marshal(b, m, deterministic) +} +func (m *HealthResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_HealthResponse.Merge(m, src) +} +func (m *HealthResponse) XXX_Size() int { + return xxx_messageInfo_HealthResponse.Size(m) +} +func (m *HealthResponse) XXX_DiscardUnknown() { + xxx_messageInfo_HealthResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_HealthResponse proto.InternalMessageInfo + +func (m *HealthResponse) GetStatus() string { + if m != nil { + return m.Status + } + return "" +} + +type StatsRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *StatsRequest) Reset() { *m = StatsRequest{} } +func (m *StatsRequest) String() string { return proto.CompactTextString(m) } +func (*StatsRequest) ProtoMessage() {} +func (*StatsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_f25415e61bccfa1f, []int{2} +} + +func (m *StatsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_StatsRequest.Unmarshal(m, b) +} +func (m *StatsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_StatsRequest.Marshal(b, m, deterministic) +} +func (m *StatsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_StatsRequest.Merge(m, src) +} +func (m *StatsRequest) XXX_Size() int { + return xxx_messageInfo_StatsRequest.Size(m) +} +func (m *StatsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_StatsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_StatsRequest proto.InternalMessageInfo + +type StatsResponse struct { + // unix timestamp + Started uint64 `protobuf:"varint,1,opt,name=started,proto3" json:"started,omitempty"` + // in seconds + Uptime uint64 `protobuf:"varint,2,opt,name=uptime,proto3" json:"uptime,omitempty"` + // in bytes + Memory uint64 `protobuf:"varint,3,opt,name=memory,proto3" json:"memory,omitempty"` + // num threads + Threads uint64 `protobuf:"varint,4,opt,name=threads,proto3" json:"threads,omitempty"` + // total gc in nanoseconds + Gc uint64 `protobuf:"varint,5,opt,name=gc,proto3" json:"gc,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *StatsResponse) Reset() { *m = StatsResponse{} } +func (m *StatsResponse) String() string { return proto.CompactTextString(m) } +func (*StatsResponse) ProtoMessage() {} +func (*StatsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f25415e61bccfa1f, []int{3} +} + +func (m *StatsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_StatsResponse.Unmarshal(m, b) +} +func (m *StatsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_StatsResponse.Marshal(b, m, deterministic) +} +func (m *StatsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_StatsResponse.Merge(m, src) +} +func (m *StatsResponse) XXX_Size() int { + return xxx_messageInfo_StatsResponse.Size(m) +} +func (m *StatsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_StatsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_StatsResponse proto.InternalMessageInfo + +func (m *StatsResponse) GetStarted() uint64 { + if m != nil { + return m.Started + } + return 0 +} + +func (m *StatsResponse) GetUptime() uint64 { + if m != nil { + return m.Uptime + } + return 0 +} + +func (m *StatsResponse) GetMemory() uint64 { + if m != nil { + return m.Memory + } + return 0 +} + +func (m *StatsResponse) GetThreads() uint64 { + if m != nil { + return m.Threads + } + return 0 +} + +func (m *StatsResponse) GetGc() uint64 { + if m != nil { + return m.Gc + } + return 0 +} + +func init() { + proto.RegisterType((*HealthRequest)(nil), "HealthRequest") + proto.RegisterType((*HealthResponse)(nil), "HealthResponse") + proto.RegisterType((*StatsRequest)(nil), "StatsRequest") + proto.RegisterType((*StatsResponse)(nil), "StatsResponse") +} + +func init() { + proto.RegisterFile("micro/go-micro/debug/proto/debug.proto", fileDescriptor_f25415e61bccfa1f) +} + +var fileDescriptor_f25415e61bccfa1f = []byte{ + // 230 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0x41, 0x4b, 0xc4, 0x30, + 0x10, 0x85, 0xb7, 0x75, 0x5b, 0x71, 0xb0, 0x59, 0xc8, 0x41, 0xc2, 0x9e, 0x24, 0x07, 0x29, 0x88, + 0x59, 0xd0, 0xbf, 0xe0, 0xc1, 0x73, 0xbd, 0x0b, 0xd9, 0x76, 0xe8, 0x16, 0xac, 0xa9, 0x99, 0xe9, + 0xc1, 0xb3, 0x7f, 0x5c, 0x9a, 0xa4, 0x60, 0x6f, 0xef, 0xbd, 0xf0, 0x1e, 0xf9, 0x06, 0x1e, 0xc6, + 0xa1, 0xf5, 0xee, 0xd4, 0xbb, 0xa7, 0x28, 0x3a, 0x3c, 0xcf, 0xfd, 0x69, 0xf2, 0x8e, 0x93, 0x36, + 0x41, 0xeb, 0x03, 0x54, 0x6f, 0x68, 0x3f, 0xf9, 0xd2, 0xe0, 0xf7, 0x8c, 0xc4, 0xba, 0x06, 0xb1, + 0x06, 0x34, 0xb9, 0x2f, 0x42, 0x79, 0x07, 0x25, 0xb1, 0xe5, 0x99, 0x54, 0x76, 0x9f, 0xd5, 0x37, + 0x4d, 0x72, 0x5a, 0xc0, 0xed, 0x3b, 0x5b, 0xa6, 0xb5, 0xf9, 0x9b, 0x41, 0x95, 0x82, 0xd4, 0x54, + 0x70, 0x4d, 0x6c, 0x3d, 0x63, 0x17, 0xaa, 0xfb, 0x66, 0xb5, 0xcb, 0xe6, 0x3c, 0xf1, 0x30, 0xa2, + 0xca, 0xc3, 0x43, 0x72, 0x4b, 0x3e, 0xe2, 0xe8, 0xfc, 0x8f, 0xba, 0x8a, 0x79, 0x74, 0xcb, 0x12, + 0x5f, 0x3c, 0xda, 0x8e, 0xd4, 0x3e, 0x2e, 0x25, 0x2b, 0x05, 0xe4, 0x7d, 0xab, 0x8a, 0x10, 0xe6, + 0x7d, 0xfb, 0xfc, 0x01, 0xc5, 0xeb, 0xc2, 0x27, 0x1f, 0xa1, 0x8c, 0x20, 0x52, 0x98, 0x0d, 0xe2, + 0xf1, 0x60, 0xb6, 0x84, 0x7a, 0x27, 0x6b, 0x28, 0xc2, 0xd7, 0x65, 0x65, 0xfe, 0x33, 0x1d, 0x85, + 0xd9, 0x10, 0xe9, 0xdd, 0xb9, 0x0c, 0x77, 0x7b, 0xf9, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xfe, 0xb9, + 0x5f, 0xf7, 0x61, 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 + +// DebugClient is the client API for Debug service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type DebugClient interface { + Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error) + Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) +} + +type debugClient struct { + cc *grpc.ClientConn +} + +func NewDebugClient(cc *grpc.ClientConn) DebugClient { + return &debugClient{cc} +} + +func (c *debugClient) Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error) { + out := new(HealthResponse) + err := c.cc.Invoke(ctx, "/Debug/Health", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *debugClient) Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) { + out := new(StatsResponse) + err := c.cc.Invoke(ctx, "/Debug/Stats", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DebugServer is the server API for Debug service. +type DebugServer interface { + Health(context.Context, *HealthRequest) (*HealthResponse, error) + Stats(context.Context, *StatsRequest) (*StatsResponse, error) +} + +func RegisterDebugServer(s *grpc.Server, srv DebugServer) { + s.RegisterService(&_Debug_serviceDesc, srv) +} + +func _Debug_Health_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DebugServer).Health(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/Debug/Health", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DebugServer).Health(ctx, req.(*HealthRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Debug_Stats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DebugServer).Stats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/Debug/Stats", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DebugServer).Stats(ctx, req.(*StatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Debug_serviceDesc = grpc.ServiceDesc{ + ServiceName: "Debug", + HandlerType: (*DebugServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Health", + Handler: _Debug_Health_Handler, + }, + { + MethodName: "Stats", + Handler: _Debug_Stats_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "micro/go-micro/debug/proto/debug.proto", +} diff --git a/server/debug/proto/debug.proto b/debug/proto/debug.proto similarity index 59% rename from server/debug/proto/debug.proto rename to debug/proto/debug.proto index 8d96192e..b642cd41 100644 --- a/server/debug/proto/debug.proto +++ b/debug/proto/debug.proto @@ -1,13 +1,9 @@ syntax = "proto3"; -// This is commented out due to import cycles. -// But its what we expect the RPC service to -// return. -// -// service Debug { -// rpc Health(HealthRequest) returns (HealthResponse) {} -// rpc Stats(StatsRequest) returns (StatsResponse) {} -// } +service Debug { + rpc Health(HealthRequest) returns (HealthResponse) {} + rpc Stats(StatsRequest) returns (StatsResponse) {} +} message HealthRequest { } diff --git a/function_test.go b/function_test.go index dd85590f..a7d1b18d 100644 --- a/function_test.go +++ b/function_test.go @@ -5,8 +5,8 @@ import ( "sync" "testing" + proto "github.com/micro/go-micro/debug/proto" "github.com/micro/go-micro/registry/memory" - proto "github.com/micro/go-micro/server/debug/proto" ) func TestFunction(t *testing.T) { diff --git a/go.mod b/go.mod index a9590f1d..83d4af10 100644 --- a/go.mod +++ b/go.mod @@ -3,42 +3,78 @@ module github.com/micro/go-micro go 1.12 require ( + cloud.google.com/go v0.43.0 // indirect github.com/BurntSushi/toml v0.3.1 + github.com/Microsoft/go-winio v0.4.14 // indirect + github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect + github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect + github.com/armon/go-radix v1.0.0 // indirect github.com/beevik/ntp v0.2.0 github.com/bitly/go-simplejson v0.5.0 github.com/bwmarrin/discordgo v0.19.0 + github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c github.com/fsnotify/fsnotify v1.4.7 - github.com/fsouza/go-dockerclient v1.4.1 + github.com/fsouza/go-dockerclient v1.4.2 github.com/ghodss/yaml v1.0.0 + github.com/go-kit/kit v0.9.0 // indirect github.com/go-log/log v0.1.0 github.com/go-playground/locales v0.12.1 // indirect github.com/go-playground/universal-translator v0.16.0 // indirect - github.com/golang/protobuf v1.3.1 + github.com/golang/protobuf v1.3.2 + github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70 // indirect github.com/google/uuid v1.1.1 - github.com/gorilla/handlers v1.4.0 + github.com/gorilla/handlers v1.4.2 github.com/gorilla/websocket v1.4.0 github.com/hashicorp/consul/api v1.1.0 + github.com/hashicorp/go-immutable-radix v1.1.0 // indirect + github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-retryablehttp v0.5.4 // indirect + github.com/hashicorp/go-rootcerts v1.0.1 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-version v1.2.0 // indirect + github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/hashicorp/hcl v1.0.0 + github.com/hashicorp/mdns v1.0.1 // indirect github.com/hashicorp/memberlist v0.1.4 + github.com/hashicorp/serf v0.8.3 // indirect github.com/imdario/mergo v0.3.7 github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1 - github.com/json-iterator/go v1.1.6 + github.com/json-iterator/go v1.1.7 + github.com/kisielk/errcheck v1.2.0 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/leodido/go-urn v1.1.0 // indirect - github.com/lucas-clemente/quic-go v0.11.2 + github.com/lucas-clemente/quic-go v0.12.0 + github.com/mattn/go-colorable v0.1.2 // indirect + github.com/mattn/go-runewidth v0.0.4 // indirect github.com/micro/cli v0.2.0 - github.com/micro/mdns v0.1.0 + github.com/micro/mdns v0.2.0 + github.com/miekg/dns v1.1.15 // indirect + github.com/mitchellh/gox v1.0.1 // indirect github.com/mitchellh/hashstructure v1.0.0 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/nats-io/nats.go v1.8.1 + github.com/nats-io/nkeys v0.1.0 // indirect github.com/nlopes/slack v0.5.0 + github.com/olekukonko/tablewriter v0.0.1 + github.com/onsi/ginkgo v1.8.0 // indirect + github.com/onsi/gomega v1.5.0 // indirect github.com/pkg/errors v0.8.1 + github.com/posener/complete v1.2.1 // indirect + github.com/prometheus/client_golang v1.1.0 // indirect + github.com/sirupsen/logrus v1.4.2 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect - golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 - golang.org/x/net v0.0.0-20190607181551-461777fb6f67 - google.golang.org/grpc v1.21.1 - gopkg.in/go-playground/validator.v9 v9.29.0 - gopkg.in/src-d/go-git.v4 v4.11.0 + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + golang.org/x/mobile v0.0.0-20190806162312-597adff16ade // indirect + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 + golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa // indirect + golang.org/x/tools v0.0.0-20190807201305-8be58fba6352 // indirect + google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 // indirect + google.golang.org/grpc v1.22.1 + gopkg.in/go-playground/validator.v9 v9.29.1 + gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/telegram-bot-api.v4 v4.6.4 + honnef.co/go/tools v0.0.1-2019.2.2 // indirect ) diff --git a/go.sum b/go.sum index 380c6df5..fbe88f4c 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,38 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro= +cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +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= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= @@ -18,19 +40,30 @@ github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5a github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= 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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M= github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16 h1:dmUn0SuGx7unKFwxyeQ/oLUHhEfZosEDrpmYM+6MTuc= github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20190710153559-aa8249ae1b8b h1:+Ga+YpCDpcY1fln6GI0fiiirpqHGcob5/Vk3oKNuGdU= +github.com/docker/docker v1.4.2-0.20190710153559-aa8249ae1b8b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo= github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c h1:pBgVXWDXju1m8W4lnEeIqTHPOzhTUO81a7yknM/xQR4= @@ -39,33 +72,61 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/go-dockerclient v1.4.1 h1:W7wuJ3IB48WYZv/UBk9dCTIb9oX805+L9KIm65HcUYs= github.com/fsouza/go-dockerclient v1.4.1/go.mod h1:PUNHxbowDqRXfRgZqMz1OeGtbWC6VKyZvJ99hDjB0qs= +github.com/fsouza/go-dockerclient v1.4.2 h1:dl6GfIWS5Qn4C6OfSnnoe6YuOV8lvKAE8W/YD1Q7udo= +github.com/fsouza/go-dockerclient v1.4.2/go.mod h1:COunfLZrsdwX/j3YVDAG8gIw3KutrI0x1+vGEJ5zxdI= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U= github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +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 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.4.1 h1:BHvcRGJe/TrL+OqFxoKQGddTgeibiOjaBssV5a/N9sw= +github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= @@ -73,33 +134,53 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs= github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.8.3 h1:MWYcmct5EtKz0efYooPcL0yNkem+7kWxqXDi/UIh+8k= +github.com/hashicorp/serf v0.8.3/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd h1:anPrsicrIi2ColgWTVPk+TrN42hJIWlfPHSBP9S0ZkM= github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd/go.mod h1:3LVOLeyx9XVvwPgrt2be44XgSqndprz1G18rSk8KD84= @@ -112,55 +193,106 @@ github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1 h1:lnrOS18wZBYrzdD github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1/go.mod h1:DFXrEwSRX0p/aSvxE21319menCBFeQO0jXpRj7LEZUA= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo= +github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/lucas-clemente/quic-go v0.7.1-0.20190710050138-1441923ab031 h1:wjcGvgllMOQw8wNYFH6acq/KlTAdjKMSo1EUYybHXto= +github.com/lucas-clemente/quic-go v0.7.1-0.20190710050138-1441923ab031/go.mod h1:lb5aAxL68VvhZ00e7yYuQVK/9FLggtYy4qo7oI5qzqA= github.com/lucas-clemente/quic-go v0.11.2 h1:Mop0ac3zALaBR3wGs6j8OYe/tcFvFsxTUFMkE/7yUOI= github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= +github.com/lucas-clemente/quic-go v0.12.0 h1:dYHUyB50gEQlK3KqytmNySzuyzAcaQ3iuI2ZReAfVrE= +github.com/lucas-clemente/quic-go v0.12.0/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s= +github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA= github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= +github.com/marten-seemann/qtls v0.2.4 h1:mCJ6i1jAqcsm9XODrSGvXECodoAb1STta+TkxJCwCnE= +github.com/marten-seemann/qtls v0.2.4/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= +github.com/marten-seemann/qtls v0.3.1 h1:ySYIvhFjFY2JsNHY6VACvomMEDy3EvdPA6yciUFAiHw= +github.com/marten-seemann/qtls v0.3.1/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= +github.com/marten-seemann/qtls v0.3.2 h1:O7awy4bHEzSX/K3h+fZig3/Vo03s/RxlxgsAk9sYamI= +github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/micro/cli v0.2.0 h1:ut3rV5JWqZjsXIa2MvGF+qMUP8DAUTvHX9Br5gO4afA= github.com/micro/cli v0.2.0/go.mod h1:jRT9gmfVKWSS6pkKcXQ8YhUyj6bzwxK8Fp5b0Y7qNnk= github.com/micro/mdns v0.1.0 h1:fuLybUsfynbigJmCot/54i+gwe0hpc/vtCMvWt2WfDI= github.com/micro/mdns v0.1.0/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc= +github.com/micro/mdns v0.1.1-0.20190729112526-ef68c9635478 h1:L6jnZZ763dMLlvst8P0dWHa1WbUu7ppUY1q3AY2hhIU= +github.com/micro/mdns v0.1.1-0.20190729112526-ef68c9635478/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc= +github.com/micro/mdns v0.2.0 h1:/+/n2PSiJURrXsBIGtfCz0hex/XYKqVsn51GAGdFrOE= +github.com/micro/mdns v0.2.0/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA= +github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI= +github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +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-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/nats.go v1.8.1 h1:6lF/f1/NN6kzUDBz6pyvQDEXO39jqXcWRLu/tKjtOUQ= github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= github.com/nats-io/nkeys v0.0.2 h1:+qM7QpgXnvDDixitZtQUBDY9w/s9mu1ghS+JIbsrx6M= github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= +github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= +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/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 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= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= @@ -168,6 +300,7 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -175,75 +308,232 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= +github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU= +golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190618124811-92942e4437e2/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190607214518-6fa95d984e88/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20190806162312-597adff16ade/go.mod h1:AlhUtkH4DA4asiFC5RgK7ZKmauvtkAVcy9L0epCzlWo= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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= +golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00= golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190310054646-10058d7d4faa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190621062556-bf70e4678053 h1:T0MJjz97TtCXa3ZNW2Oenb3KQWB91K965zMEbIJ4ThA= +golang.org/x/sys v0.0.0-20190621062556-bf70e4678053/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ= +golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/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-20190804053845-51ab0e2deafa h1:KIDDMLT1O0Nr7TSxp8xM5tJcdn8tgyAONntO829og1M= +golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190530171427-2b03ca6e44eb/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190620191750-1fa568393b23/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190710184609-286818132824/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190807201305-8be58fba6352/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +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 h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= 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= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 h1:9VBRTdmgQxbs6HE0sUnMrSWNePppAJU07NYvX5dIB04= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 h1:5pOB7se0B2+IssELuQUs6uoBgYJenkU2AQlvopc2sRw= +google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/validator.v9 v9.29.0 h1:5ofssLNYgAA/inWn6rTZ4juWpRJUwEnXc1LG2IeXwgQ= gopkg.in/go-playground/validator.v9 v9.29.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo= gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= +gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek= +gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= +gopkg.in/src-d/go-billy.v4 v4.3.1 h1:OkK1DmefDy1Z6Veu82wdNj/cLpYORhdX4qdaYCPwc7s= +gopkg.in/src-d/go-billy.v4 v4.3.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.11.0 h1:cJwWgJ0DXifrNrXM6RGN1Y2yR60Rr1zQ9Q5DX5S9qgU= gopkg.in/src-d/go-git.v4 v4.11.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= +gopkg.in/src-d/go-git.v4 v4.12.0 h1:CKgvBCJCcdfNnyXPYI4Cp8PaDDAmAPEN0CtfEdEAbd8= +gopkg.in/src-d/go-git.v4 v4.12.0/go.mod h1:zjlNnzc1Wjn43v3Mtii7RVxiReNP0fIu9npcXKzuNp4= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= gopkg.in/telegram-bot-api.v4 v4.6.4 h1:hpHWhzn4jTCsAJZZ2loNKfy2QWyPDRJVl3aTFXeMW8g= gopkg.in/telegram-bot-api.v4 v4.6.4/go.mod h1:5DpGO5dbumb40px+dXcwCpcjmeHNYLpk0bp3XRNvWDM= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -254,3 +544,9 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190614002413-cb51c254f01b/go.mod h1:JlmFZigtG9vBVR3QGIQ9g/Usz4BzH+Xm6Z8iHQWRYUw= +honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/monitor/default.go b/monitor/default.go new file mode 100644 index 00000000..ac89d2e3 --- /dev/null +++ b/monitor/default.go @@ -0,0 +1,311 @@ +package monitor + +import ( + "context" + "errors" + "sync" + "time" + + "github.com/micro/go-micro/client" + pb "github.com/micro/go-micro/debug/proto" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/registry/cache" +) + +type monitor struct { + options Options + + exit chan bool + registry cache.Cache + client client.Client + + sync.RWMutex + running bool + services map[string]*Status +} + +// check provides binary running/failed status. +// In the event Debug.Health cannot be called on a service we reap the node. +func (m *monitor) check(service string) (*Status, error) { + services, err := m.registry.GetService(service) + if err != nil { + return nil, err + } + + // create debug client + debug := pb.NewDebugService(service, m.client) + + var status *Status + var gerr error + + // iterate through multiple versions of a service + for _, service := range services { + for _, node := range service.Nodes { + rsp, err := debug.Health( + context.Background(), + // empty health request + &pb.HealthRequest{}, + // call this specific node + client.WithAddress(node.Address), + // retry in the event of failure + client.WithRetries(3), + ) + if err != nil { + // reap the dead node + m.registry.Deregister(®istry.Service{ + Name: service.Name, + Version: service.Version, + Nodes: []*registry.Node{node}, + }) + + // save the error + gerr = err + continue + } + + // expecting ok response status + if rsp.Status != "ok" { + gerr = errors.New(rsp.Status) + continue + } + + // no error set status + status = &Status{ + Code: StatusRunning, + Info: "running", + } + } + } + + // if we got the success case return it + if status != nil { + return status, nil + } + + // if gerr is not nil return it + if gerr != nil { + return &Status{ + Code: StatusFailed, + Info: "not running", + Error: gerr.Error(), + }, nil + } + + // otherwise unknown status + return &Status{ + Code: StatusUnknown, + Info: "unknown status", + }, nil +} + +func (m *monitor) reap() { + services, err := m.registry.ListServices() + if err != nil { + return + } + + serviceMap := make(map[string]bool) + for _, service := range services { + serviceMap[service.Name] = true + } + + m.Lock() + defer m.Unlock() + + // range over our watched services + for service, _ := range m.services { + // check if the service exists in the registry + if !serviceMap[service] { + // if not, delete it in our status map + delete(m.services, service) + } + } +} + +func (m *monitor) run() { + // check the status every tick + t := time.NewTicker(time.Minute) + defer t.Stop() + + // reap dead services + t2 := time.NewTicker(time.Hour) + defer t2.Stop() + + // list the known services + services, _ := m.registry.ListServices() + + // create a check chan of same length + check := make(chan string, len(services)) + + // front-load the services to watch + for _, service := range services { + check <- service.Name + } + + for { + select { + // exit if we're told to + case <-m.exit: + return + // check a service when told to + case service := <-check: + // check the status + status, err := m.check(service) + if err != nil { + status = &Status{ + Code: StatusUnknown, + Info: "unknown status", + } + } + + // save the status + m.Lock() + m.services[service] = status + m.Unlock() + // on the tick interval get all services and issue a check + case <-t.C: + // create a list of services + serviceMap := make(map[string]bool) + + m.RLock() + for service, _ := range m.services { + serviceMap[service] = true + } + m.RUnlock() + + go func() { + // check the status of all watched services + for service, _ := range serviceMap { + select { + case <-m.exit: + return + case check <- service: + default: + // barf if we block + } + } + + // list services + services, _ := m.registry.ListServices() + + for _, service := range services { + // start watching the service + if ok := serviceMap[service.Name]; !ok { + m.Watch(service.Name) + } + } + }() + case <-t2.C: + // reap any dead/non-existent services + m.reap() + } + } +} + +func (m *monitor) Reap(service string) error { + services, err := m.registry.GetService(service) + if err != nil { + return nil + } + m.Lock() + defer m.Unlock() + delete(m.services, service) + for _, service := range services { + m.registry.Deregister(service) + } + return nil +} + +func (m *monitor) Status(service string) (Status, error) { + m.RLock() + defer m.RUnlock() + if status, ok := m.services[service]; ok { + return *status, nil + } + return Status{}, ErrNotWatching +} + +func (m *monitor) Watch(service string) error { + m.Lock() + defer m.Unlock() + + // check if we're watching + if _, ok := m.services[service]; ok { + return nil + } + + // get the status + status, err := m.check(service) + if err != nil { + return err + } + + // set the status + m.services[service] = status + return nil +} + +func (m *monitor) Run() error { + m.Lock() + defer m.Unlock() + + if m.running { + return nil + } + + // reset the exit channel + m.exit = make(chan bool) + // setup a new cache + m.registry = cache.New(m.options.Registry) + + // start running + go m.run() + + // set to running + m.running = true + + return nil +} + +func (m *monitor) Stop() error { + m.Lock() + defer m.Unlock() + + if !m.running { + return nil + } + + select { + case <-m.exit: + return nil + default: + close(m.exit) + for s, _ := range m.services { + delete(m.services, s) + } + m.registry.Stop() + m.running = false + return nil + } + + return nil +} + +func newMonitor(opts ...Option) Monitor { + options := Options{ + Client: client.DefaultClient, + Registry: registry.DefaultRegistry, + } + + for _, o := range opts { + o(&options) + } + + return &monitor{ + options: options, + exit: make(chan bool), + client: options.Client, + registry: cache.New(options.Registry), + services: make(map[string]*Status), + } +} diff --git a/monitor/default_test.go b/monitor/default_test.go new file mode 100644 index 00000000..335df305 --- /dev/null +++ b/monitor/default_test.go @@ -0,0 +1,37 @@ +package monitor + +import ( + "testing" +) + +func TestMonitor(t *testing.T) { + // create new monitor + m := NewMonitor() + + if err := m.Run(); err != nil { + t.Fatalf("failed to stop monitor: %v", err) + } + + services := []string{"foo", "bar", "baz"} + + for _, service := range services { + _, err := m.Status(service) + if err == nil { + t.Fatal("expected status error for unknown service") + } + + if err := m.Watch(service); err == nil { + t.Fatal("expected watch error for unknown service") + } + + // TODO: + // 1. start a service + // 2. watch service + // 3. get service status + } + + // stop monitor + if err := m.Stop(); err != nil { + t.Fatalf("failed to stop monitor: %v", err) + } +} diff --git a/monitor/monitor.go b/monitor/monitor.go new file mode 100644 index 00000000..711d7d03 --- /dev/null +++ b/monitor/monitor.go @@ -0,0 +1,43 @@ +// Package monitor monitors service health +package monitor + +import ( + "errors" +) + +const ( + StatusUnknown StatusCode = iota + StatusRunning + StatusFailed +) + +type StatusCode int + +// Monitor monitors a service and reaps dead instances +type Monitor interface { + // Reap a service and stop monitoring + Reap(service string) error + // Status of the service + Status(service string) (Status, error) + // Watch starts watching the service + Watch(service string) error + // Run the monitor to watch all services + Run() error + // Stop monitoring + Stop() error +} + +type Status struct { + Code StatusCode + Info string + Error string +} + +var ( + ErrNotWatching = errors.New("not watching") +) + +// NewMonitor returns a new monitor +func NewMonitor(opts ...Option) Monitor { + return newMonitor(opts...) +} diff --git a/monitor/options.go b/monitor/options.go new file mode 100644 index 00000000..445d39d9 --- /dev/null +++ b/monitor/options.go @@ -0,0 +1,25 @@ +package monitor + +import ( + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/registry" +) + +type Options struct { + Client client.Client + Registry registry.Registry +} + +type Option func(*Options) + +func Client(c client.Client) Option { + return func(o *Options) { + o.Client = c + } +} + +func Registry(r registry.Registry) Option { + return func(o *Options) { + o.Registry = r + } +} diff --git a/network/link/default.go b/network/link/default.go new file mode 100644 index 00000000..f6742b83 --- /dev/null +++ b/network/link/default.go @@ -0,0 +1,288 @@ +// Package link provides a measured transport.Socket link +package link + +import ( + "io" + "sync" + "time" + + "github.com/micro/go-micro/config/options" + "github.com/micro/go-micro/transport" +) + +type link struct { + sync.RWMutex + + // the link id + id string + + // the remote end to dial + addr string + + // channel used to close the link + closed chan bool + + // if its connected + connected bool + + // the transport to use + transport transport.Transport + + // the send queue to the socket + sendQueue chan *transport.Message + // the recv queue to the socket + recvQueue chan *transport.Message + + // the socket for this link + socket transport.Socket + + // determines the cost of the link + // based on queue length and roundtrip + length int + weight int +} + +func newLink(options options.Options) *link { + // default values + var sock transport.Socket + var addr string + id := "local" + tr := transport.DefaultTransport + + lid, ok := options.Values().Get("link.id") + if ok { + id = lid.(string) + } + + laddr, ok := options.Values().Get("link.address") + if ok { + addr = laddr.(string) + } + + ltr, ok := options.Values().Get("link.transport") + if ok { + tr = ltr.(transport.Transport) + } + + lsock, ok := options.Values().Get("link.socket") + if ok { + sock = lsock.(transport.Socket) + } + + l := &link{ + // the remote end to dial + addr: addr, + // transport to dial link + transport: tr, + // the socket to use + // this is nil if not specified + socket: sock, + // unique id assigned to the link + id: id, + // the closed channel used to close the conn + closed: make(chan bool), + // then send queue + sendQueue: make(chan *transport.Message, 128), + // the receive queue + recvQueue: make(chan *transport.Message, 128), + } + + // return the link + return l +} + +// link methods + +// process processes messages on the send and receive queues. +func (l *link) process() { + go func() { + for { + m := new(transport.Message) + if err := l.recv(m); err != nil { + return + } + + select { + case l.recvQueue <- m: + case <-l.closed: + return + } + } + }() + + // messages sent + i := 0 + length := 0 + + for { + select { + case m := <-l.sendQueue: + t := time.Now() + + // send the message + if err := l.send(m); err != nil { + return + } + + // get header size, body size and time taken + hl := len(m.Header) + bl := len(m.Body) + d := time.Since(t) + + // don't calculate on empty messages + if hl == 0 && bl == 0 { + continue + } + + // increment sent + i++ + + // time take to send some bits and bytes + td := float64(hl+bl) / float64(d.Nanoseconds()) + // increase the scale + td += 1 + + // judge the length + length = int(td) / (length + int(td)) + + // every 10 messages update length + if (i % 10) == 1 { + // cost average the length + // save it + l.Lock() + l.length = length + l.Unlock() + } + case <-l.closed: + return + } + } +} + +// send a message over the link +func (l *link) send(m *transport.Message) error { + // TODO: measure time taken and calculate length/rate + // send via the transport socket + return l.socket.Send(m) +} + +// recv a message on the link +func (l *link) recv(m *transport.Message) error { + if m.Header == nil { + m.Header = make(map[string]string) + } + // receive the transport message + return l.socket.Recv(m) +} + +// Connect attempts to connect to an address and sets the socket +func (l *link) Connect() error { + l.Lock() + if l.connected { + l.Unlock() + return nil + } + defer l.Unlock() + + // replace closed + l.closed = make(chan bool) + + // assume existing socket + if len(l.addr) == 0 { + go l.process() + return nil + } + + // dial the endpoint + c, err := l.transport.Dial(l.addr) + if err != nil { + return err + } + + // set the socket + l.socket = c + + // kick start the processing + go l.process() + + return nil +} + +// Close the link +func (l *link) Close() error { + select { + case <-l.closed: + return nil + default: + close(l.closed) + l.Lock() + l.connected = false + l.Unlock() + return l.socket.Close() + } +} + +// returns the node id +func (l *link) Id() string { + l.RLock() + defer l.RUnlock() + return l.id +} + +// the remote ip of the link +func (l *link) Remote() string { + l.RLock() + defer l.RUnlock() + return l.socket.Remote() +} + +// the local ip of the link +func (l *link) Local() string { + l.RLock() + defer l.RUnlock() + return l.socket.Local() +} + +// length/rate of the link +func (l *link) Length() int { + l.RLock() + defer l.RUnlock() + return l.length +} + +// weight checks the size of the queues +func (l *link) Weight() int { + return len(l.sendQueue) + len(l.recvQueue) +} + +// Accept accepts a message on the socket +func (l *link) Recv(m *transport.Message) error { + select { + case <-l.closed: + return io.EOF + case rm := <-l.recvQueue: + *m = *rm + return nil + } + // never reach + return nil +} + +// Send sends a message on the socket immediately +func (l *link) Send(m *transport.Message) error { + select { + case <-l.closed: + return io.EOF + case l.sendQueue <- m: + } + return nil +} + +func (l *link) Status() string { + select { + case <-l.closed: + return "closed" + default: + return "connected" + } +} diff --git a/network/link/link.go b/network/link/link.go new file mode 100644 index 00000000..3acfd772 --- /dev/null +++ b/network/link/link.go @@ -0,0 +1,60 @@ +// Package link provides a measured link on top of a transport.Socket +package link + +import ( + "errors" + + "github.com/micro/go-micro/config/options" + "github.com/micro/go-micro/transport" +) + +// Link is a layer on top of a transport socket with the +// buffering send and recv queue's with the ability to +// measure the actual transport link and reconnect if +// an address is specified. +type Link interface { + // provides the transport.Socket interface + transport.Socket + // Connect connects the link. It must be called first + // if there's an expectation to create a new socket. + Connect() error + // Id of the link is "local" if not specified + Id() string + // Status of the link + Status() string + // Depth of the buffers + Weight() int + // Rate of the link + Length() int +} + +var ( + ErrLinkClosed = errors.New("link closed") +) + +// NewLink creates a new link on top of a socket +func NewLink(opts ...options.Option) Link { + return newLink(options.NewOptions(opts...)) +} + +// Sets the link id which otherwise defaults to "local" +func Id(id string) options.Option { + return options.WithValue("link.id", id) +} + +// The address to use for the link. Connect must be +// called for this to be used, its otherwise unused. +func Address(a string) options.Option { + return options.WithValue("link.address", a) +} + +// The transport to use for the link where we +// want to dial the connection first. +func Transport(t transport.Transport) options.Option { + return options.WithValue("link.transport", t) +} + +// Socket sets the socket to use instead of dialing. +func Socket(s transport.Socket) options.Option { + return options.WithValue("link.socket", s) +} diff --git a/network/network.go b/network/network.go index adeb18cf..15d8155f 100644 --- a/network/network.go +++ b/network/network.go @@ -1,45 +1,2 @@ -// Package network is an interface for defining a network overlay +// Package network is for creating internetworks package network - -import ( - "github.com/micro/go-micro/config/options" -) - -type Network interface { - options.Options - // Id of this network - Id() string - // Connect to the network with id - Connect(id string) error - // Close the network connection - Close() error - // Accept messages - Accept() (*Message, error) - // Send a message - Send(*Message) error - // Advertise a service on this network - Advertise(service string) error - // Retrieve list of nodes for a service - Nodes(service string) ([]Node, error) -} - -// Node represents a network node -type Node interface { - // Node is a network. Network is a node. - Network -} - -// Message is a message sent over the network -type Message struct { - // Headers are the routing headers - // e.g Micro-Service, Micro-Endpoint, Micro-Network - // see https://github.com/micro/development/blob/master/protocol.md - Header map[string]string - // Body is the encaspulated payload - Body []byte -} - -var ( - // TODO: set default network - DefaultNetwork Network -) diff --git a/network/resolver/dns/dns.go b/network/resolver/dns/dns.go new file mode 100644 index 00000000..ba109656 --- /dev/null +++ b/network/resolver/dns/dns.go @@ -0,0 +1,30 @@ +// Package dns resolves names to dns srv records +package dns + +import ( + "fmt" + "net" + + "github.com/micro/go-micro/network/resolver" +) + +type Resolver struct{} + +// Resolve assumes ID is a domain name e.g micro.mu +func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) { + _, addrs, err := net.LookupSRV("network", "udp", name) + if err != nil { + return nil, err + } + var records []*resolver.Record + for _, addr := range addrs { + address := addr.Target + if addr.Port > 0 { + address = fmt.Sprintf("%s:%d", addr.Target, addr.Port) + } + records = append(records, &resolver.Record{ + Address: address, + }) + } + return records, nil +} diff --git a/network/resolver/http/http.go b/network/resolver/http/http.go new file mode 100644 index 00000000..b6025a59 --- /dev/null +++ b/network/resolver/http/http.go @@ -0,0 +1,59 @@ +// Package http resolves names to network addresses using a http request +package http + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + + "github.com/micro/go-micro/network/resolver" +) + +type Resolver struct { + // If not set, defaults to http + Proto string + + // Path sets the path to lookup. Defaults to /network + Path string +} + +// Resolve assumes ID is a domain which can be converted to a http://name/network request +func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) { + proto := "http" + path := "/network" + + if len(r.Proto) > 0 { + proto = r.Proto + } + + if len(r.Path) > 0 { + path = r.Path + } + + uri := &url.URL{ + Scheme: proto, + Path: path, + Host: name, + } + + rsp, err := http.Get(uri.String()) + if err != nil { + return nil, err + } + defer rsp.Body.Close() + + b, err := ioutil.ReadAll(rsp.Body) + if err != nil { + return nil, err + } + + // encoding format is assumed to be json + var records []*resolver.Record + + if err := json.Unmarshal(b, &records); err != nil { + return nil, err + } + + return records, nil +} diff --git a/network/resolver/registry/registry.go b/network/resolver/registry/registry.go new file mode 100644 index 00000000..9fe80bc5 --- /dev/null +++ b/network/resolver/registry/registry.go @@ -0,0 +1,37 @@ +// Package registry resolves names using the go-micro registry +package registry + +import ( + "github.com/micro/go-micro/network/resolver" + "github.com/micro/go-micro/registry" +) + +type Resolver struct { + // Registry is the registry to use otherwise we use the defaul + Registry registry.Registry +} + +// Resolve assumes ID is a domain name e.g micro.mu +func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) { + reg := r.Registry + if reg == nil { + reg = registry.DefaultRegistry + } + + services, err := reg.GetService(name) + if err != nil { + return nil, err + } + + var records []*resolver.Record + + for _, service := range services { + for _, node := range service.Nodes { + records = append(records, &resolver.Record{ + Address: node.Address, + }) + } + } + + return records, nil +} diff --git a/network/resolver/resolver.go b/network/resolver/resolver.go new file mode 100644 index 00000000..2eb0b2a2 --- /dev/null +++ b/network/resolver/resolver.go @@ -0,0 +1,15 @@ +// Package resolver resolves network names to addresses +package resolver + +// Resolver is network resolver. It's used to find network nodes +// via the name to connect to. This is done based on Network.Name(). +// Before we can be part of any network, we have to connect to it. +type Resolver interface { + // Resolve returns a list of addresses for an name + Resolve(name string) ([]*Record, error) +} + +// A resolved record +type Record struct { + Address string `json:"address"` +} diff --git a/network/transport/transport.go b/network/transport/transport.go deleted file mode 100644 index 60e6a957..00000000 --- a/network/transport/transport.go +++ /dev/null @@ -1,203 +0,0 @@ -// Package transport implements the network as a transport interface -package transport - -import ( - "context" - "time" - - "github.com/micro/go-micro/network" - "github.com/micro/go-micro/transport" - "github.com/micro/go-micro/util/backoff" -) - -type networkKey struct{} - -// Transport is a network transport -type Transport struct { - Network network.Network - options transport.Options -} - -// Socket is a transport socket -type Socket struct { - // The service - Service string - - // Send via Network.Send(Message) - Network network.Network - - // Remote/Local - remote, local string - - // the first message if its a listener - message *network.Message -} - -// Listener is a transport listener -type Listener struct { - // The local service - Service string - - // The network - Network network.Network -} - -func (s *Socket) Local() string { - return s.local -} - -func (s *Socket) Remote() string { - return s.remote -} - -func (s *Socket) Close() error { - // TODO: should it close the network? - return s.Network.Close() -} - -func (t *Transport) Init(opts ...transport.Option) error { - for _, o := range opts { - o(&t.options) - } - return nil -} - -func (t *Transport) Options() transport.Options { - return t.options -} - -func (t *Transport) Dial(service string, opts ...transport.DialOption) (transport.Client, error) { - // TODO: establish pseudo socket? - return &Socket{ - Service: service, - Network: t.Network, - remote: service, - // TODO: local - local: "local", - }, nil -} - -func (t *Transport) Listen(service string, opts ...transport.ListenOption) (transport.Listener, error) { - // TODO specify connect id - if err := t.Network.Connect("micro.mu"); err != nil { - return nil, err - } - - // advertise the service - if err := t.Network.Advertise(service); err != nil { - return nil, err - } - - return &Listener{ - Service: service, - Network: t.Network, - }, nil -} - -func (t *Transport) String() string { - return "network" -} - -func (s *Socket) Send(msg *transport.Message) error { - // TODO: set routing headers? - return s.Network.Send(&network.Message{ - Header: msg.Header, - Body: msg.Body, - }) -} - -func (s *Socket) Recv(msg *transport.Message) error { - if msg == nil { - msg = new(transport.Message) - } - - // return first message - if s.message != nil { - msg.Header = s.message.Header - msg.Body = s.message.Body - s.message = nil - return nil - } - - m, err := s.Network.Accept() - if err != nil { - return err - } - - msg.Header = m.Header - msg.Body = m.Body - return nil -} - -func (l *Listener) Addr() string { - return l.Service -} - -func (l *Listener) Close() error { - return l.Network.Close() -} - -func (l *Listener) Accept(fn func(transport.Socket)) error { - var i int - - for { - msg, err := l.Network.Accept() - if err != nil { - // increment error counter - i++ - - // break if lots of error - if i > 3 { - return err - } - - // otherwise continue - time.Sleep(backoff.Do(i)) - continue - } - - // reset - i = 0 - - // execute in go routine - go fn(&Socket{ - Service: l.Service, - Network: l.Network, - local: l.Service, - // TODO: remote - remote: "remote", - message: msg, - }) - } -} - -// NewTransport returns a new network transport. It assumes the network is already connected -func NewTransport(opts ...transport.Option) transport.Transport { - options := transport.Options{ - Context: context.Background(), - } - - for _, o := range opts { - o(&options) - } - - net, ok := options.Context.Value(networkKey{}).(network.Network) - if !ok { - net = network.DefaultNetwork - } - - return &Transport{ - options: options, - Network: net, - } -} - -// WithNetwork passes in the network -func WithNetwork(n network.Network) transport.Option { - return func(o *transport.Options) { - if o.Context == nil { - o.Context = context.Background() - } - o.Context = context.WithValue(o.Context, networkKey{}, n) - } -} diff --git a/options.go b/options.go index 4012bb49..d566a353 100644 --- a/options.go +++ b/options.go @@ -7,9 +7,9 @@ import ( "github.com/micro/cli" "github.com/micro/go-micro/broker" "github.com/micro/go-micro/client" - "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/client/selector" + "github.com/micro/go-micro/config/cmd" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" "github.com/micro/go-micro/server" "github.com/micro/go-micro/transport" ) diff --git a/proxy/README.md b/proxy/README.md deleted file mode 100644 index fdbeaf8c..00000000 --- a/proxy/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Go Proxy [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/go-proxy?status.svg)](https://godoc.org/github.com/micro/go-proxy) - -Go Proxy is a proxy library for Go Micro. - -## Overview - -Go Micro is a distributed systems framework for client/server communication. It handles the details -around discovery, fault tolerance, rpc communication, etc. We may want to leverage this in broader ecosystems -which make use of standard http or we may also want to offload a number of requirements to a single proxy. - -## Features - -- **Transparent Proxy** - Proxy requests to any micro services through a single location. Go Proxy enables -you to write Go Micro proxies which handle and forward requests. This is good for incorporating wrappers. - -- **Single Backend Router** - Enable the single backend router to proxy directly to your local app. The proxy -allows you to set a router which serves your backend service whether its http, grpc, etc. - -- **Protocol Aware Handler** - Set a request handler which speaks your app protocol to make outbound requests. -Your app may not speak the MUCP protocol so it may be easier to translate internally. - -- **Control Planes** - Additionally we support use of control planes to offload many distributed systems concerns. - * [x] [Consul](https://www.consul.io/docs/connect/native.html) - Using Connect-Native to provide secure mTLS. - * [x] [NATS](https://nats.io/) - Fully leveraging NATS as the control plane and data plane. - diff --git a/proxy/grpc/grpc.go b/proxy/grpc/grpc.go index 89f71f8b..afb2453c 100644 --- a/proxy/grpc/grpc.go +++ b/proxy/grpc/grpc.go @@ -9,7 +9,6 @@ import ( "github.com/micro/go-micro/client" "github.com/micro/go-micro/client/grpc" "github.com/micro/go-micro/codec" - "github.com/micro/go-micro/codec/bytes" "github.com/micro/go-micro/config/options" "github.com/micro/go-micro/proxy" "github.com/micro/go-micro/server" @@ -86,14 +85,8 @@ func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server } } - // read initial request - body, err := req.Read() - if err != nil { - return err - } - // create new request with raw bytes body - creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType())) + creq := p.Client.NewRequest(service, endpoint, nil, client.WithContentType(req.ContentType())) // create new stream stream, err := p.Client.Stream(ctx, creq, opts...) diff --git a/proxy/http/http.go b/proxy/http/http.go index ebea885d..61a6fc06 100644 --- a/proxy/http/http.go +++ b/proxy/http/http.go @@ -10,8 +10,8 @@ import ( "net/url" "path" - "github.com/micro/go-micro/errors" "github.com/micro/go-micro/config/options" + "github.com/micro/go-micro/errors" "github.com/micro/go-micro/proxy" "github.com/micro/go-micro/server" ) diff --git a/proxy/mucp/mucp.go b/proxy/mucp/mucp.go index ea239d45..4de363d6 100644 --- a/proxy/mucp/mucp.go +++ b/proxy/mucp/mucp.go @@ -3,14 +3,17 @@ package mucp import ( "context" + "fmt" "io" "strings" + "sync" "github.com/micro/go-micro/client" "github.com/micro/go-micro/codec" "github.com/micro/go-micro/codec/bytes" "github.com/micro/go-micro/config/options" "github.com/micro/go-micro/proxy" + "github.com/micro/go-micro/router" "github.com/micro/go-micro/server" ) @@ -20,11 +23,21 @@ type Proxy struct { // embed options options.Options - // Endpoint specified the fixed service endpoint to call. + // Endpoint specifies the fixed service endpoint to call. Endpoint string // The client to use for outbound requests Client client.Client + + // The router for routes + Router router.Router + + // A fib of routes service:address + sync.RWMutex + Routes map[string]map[uint64]router.Route + + // The channel to monitor watcher errors + errChan chan error } // read client request and write to server @@ -39,6 +52,7 @@ func readLoop(r server.Request, s client.Stream) error { if err == io.EOF { return nil } + if err != nil { return err } @@ -50,6 +64,7 @@ func readLoop(r server.Request, s client.Stream) error { Header: hdr, Body: body, } + // write the raw request err = req.Codec().Write(msg, nil) if err == io.EOF { @@ -60,28 +75,140 @@ func readLoop(r server.Request, s client.Stream) error { } } -// ServeRequest honours the server.Router interface -func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { - // set default client - if p.Client == nil { - p.Client = client.DefaultClient +// toNodes returns a list of node addresses from given routes +func toNodes(routes map[uint64]router.Route) []string { + var nodes []string + for _, node := range routes { + address := node.Address + if len(node.Gateway) > 0 { + address = node.Gateway + } + nodes = append(nodes, address) + } + return nodes +} + +func (p *Proxy) getRoute(service string) ([]string, error) { + // lookup the route cache first + p.Lock() + routes, ok := p.Routes[service] + if ok { + p.Unlock() + return toNodes(routes), nil + } + p.Routes[service] = make(map[uint64]router.Route) + p.Unlock() + + // if the router is broken return error + if status := p.Router.Status(); status.Code == router.Error { + return nil, status.Error } - opts := []client.CallOption{} + // lookup the routes in the router + results, err := p.Router.Lookup(router.NewQuery(router.QueryService(service))) + if err != nil { + return nil, err + } + // update the proxy cache + p.Lock() + for _, route := range results { + p.Routes[service][route.Hash()] = route + } + routes = p.Routes[service] + p.Unlock() + + return toNodes(routes), nil +} + +// manageRouteCache applies action on a given route to Proxy route cache +func (p *Proxy) manageRouteCache(route router.Route, action string) error { + switch action { + case "create", "update": + if _, ok := p.Routes[route.Service]; !ok { + p.Routes[route.Service] = make(map[uint64]router.Route) + } + p.Routes[route.Service][route.Hash()] = route + case "delete": + if _, ok := p.Routes[route.Service]; !ok { + return fmt.Errorf("route not found") + } + delete(p.Routes[route.Service], route.Hash()) + default: + return fmt.Errorf("unknown action: %s", action) + } + + return nil +} + +// watchRoutes watches service routes and updates proxy cache +func (p *Proxy) watchRoutes() { + // this is safe to do as the only way watchRoutes returns is + // when some error is written into error channel - we want to bail then + defer close(p.errChan) + + // route watcher + w, err := p.Router.Watch() + if err != nil { + p.errChan <- err + return + } + + for { + event, err := w.Next() + if err != nil { + p.errChan <- err + return + } + + p.Lock() + if err := p.manageRouteCache(event.Route, fmt.Sprintf("%s", event.Type)); err != nil { + // TODO: should we bail here? + p.Unlock() + continue + } + p.Unlock() + } +} + +// ServeRequest honours the server.Router interface +func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { // service name service := req.Service() endpoint := req.Endpoint() + var addresses []string - // call a specific backend + // call a specific backend endpoint either by name or address if len(p.Endpoint) > 0 { // address:port if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 { - opts = append(opts, client.WithAddress(p.Endpoint)) - // use as service name + addresses = []string{p.Endpoint} } else { + // get route for endpoint from router + addr, err := p.getRoute(p.Endpoint) + if err != nil { + return err + } + // set the address + addresses = addr + // set the name service = p.Endpoint } + } else { + // no endpoint was specified just lookup the route + // get route for endpoint from router + addr, err := p.getRoute(service) + if err != nil { + return err + } + addresses = addr + } + + var opts []client.CallOption + + // set address if available + if len(addresses) > 0 { + opts = append(opts, client.WithAddress(addresses...)) } // read initial request @@ -106,28 +233,39 @@ func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server // get raw response resp := stream.Response() + // route watcher error + var watchErr error + // create server response write loop for { - // read backend response body - body, err := resp.Read() - if err == io.EOF { - return nil - } else if err != nil { - return err - } + select { + case err := <-p.errChan: + if err != nil { + watchErr = err + } + return watchErr + default: + // read backend response body + body, err := resp.Read() + if err == io.EOF { + return nil + } else if err != nil { + return err + } - // read backend response header - hdr := resp.Header() + // read backend response header + hdr := resp.Header() - // write raw response header to client - rsp.WriteHeader(hdr) + // write raw response header to client + rsp.WriteHeader(hdr) - // write raw response body to client - err = rsp.Write(body) - if err == io.EOF { - return nil - } else if err != nil { - return err + // write raw response body to client + err = rsp.Write(body) + if err == io.EOF { + return nil + } else if err != nil { + return err + } } } @@ -160,5 +298,28 @@ func NewProxy(opts ...options.Option) proxy.Proxy { p.Client = c.(client.Client) } + // set the default client + if p.Client == nil { + p.Client = client.DefaultClient + } + + // get router + r, ok := p.Options.Values().Get("proxy.router") + if ok { + p.Router = r.(router.Router) + } + + // create default router and start it + if p.Router == nil { + p.Router = router.DefaultRouter + } + + // routes cache + p.Routes = make(map[string]map[uint64]router.Route) + + // watch router service routes + p.errChan = make(chan error, 1) + go p.watchRoutes() + return p } diff --git a/proxy/proxy.go b/proxy/proxy.go index bb8fcc3b..cec226ba 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -6,6 +6,7 @@ import ( "github.com/micro/go-micro/client" "github.com/micro/go-micro/config/options" + "github.com/micro/go-micro/router" "github.com/micro/go-micro/server" ) @@ -29,3 +30,8 @@ func WithEndpoint(e string) options.Option { func WithClient(c client.Client) options.Option { return options.WithValue("proxy.client", c) } + +// WithRouter specifies the router to use +func WithRouter(r router.Router) options.Option { + return options.WithValue("proxy.router", r) +} diff --git a/registry/cache/rcache.go b/registry/cache/rcache.go index 09ff3c48..14bbbfb9 100644 --- a/registry/cache/rcache.go +++ b/registry/cache/rcache.go @@ -80,41 +80,6 @@ func (c *cache) quit() bool { } } -// cp copies a service. Because we're caching handing back pointers would -// create a race condition, so we do this instead its fast enough -func (c *cache) cp(current []*registry.Service) []*registry.Service { - var services []*registry.Service - - for _, service := range current { - // copy service - s := new(registry.Service) - *s = *service - - // copy nodes - var nodes []*registry.Node - for _, node := range service.Nodes { - n := new(registry.Node) - *n = *node - nodes = append(nodes, n) - } - s.Nodes = nodes - - // copy endpoints - var eps []*registry.Endpoint - for _, ep := range service.Endpoints { - e := new(registry.Endpoint) - *e = *ep - eps = append(eps, e) - } - s.Endpoints = eps - - // append service - services = append(services, s) - } - - return services -} - func (c *cache) del(service string) { delete(c.cache, service) delete(c.ttls, service) @@ -132,7 +97,7 @@ func (c *cache) get(service string) ([]*registry.Service, error) { // got services && within ttl so return cache if c.isValid(services, ttl) { // make a copy - cp := c.cp(services) + cp := registry.Copy(services) // unlock the read c.RUnlock() // return servics @@ -149,7 +114,7 @@ func (c *cache) get(service string) ([]*registry.Service, error) { // cache results c.Lock() - c.set(service, c.cp(services)) + c.set(service, registry.Copy(services)) c.Unlock() return services, nil @@ -360,18 +325,27 @@ func (c *cache) run(service string) { // watch loops the next event and calls update // it returns if there's an error func (c *cache) watch(w registry.Watcher) error { - defer w.Stop() + // used to stop the watch + stop := make(chan bool) // manage this loop go func() { + defer w.Stop() + + select { // wait for exit - <-c.exit - w.Stop() + case <-c.exit: + return + // we've been stopped + case <-stop: + return + } }() for { res, err := w.Next() if err != nil { + close(stop) return err } c.update(res) diff --git a/registry/consul/consul.go b/registry/consul/consul.go index 89f0a2d0..4b6fa770 100644 --- a/registry/consul/consul.go +++ b/registry/consul/consul.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "runtime" + "strconv" "sync" "time" @@ -16,10 +17,12 @@ import ( ) type consulRegistry struct { - Address string - Client *consul.Client + Address []string opts registry.Options + client *consul.Client + config *consul.Config + // connect enabled connect bool @@ -94,24 +97,33 @@ func configure(c *consulRegistry, opts ...registry.Option) { } // check if there are any addrs - if len(c.opts.Addrs) > 0 { - addr, port, err := net.SplitHostPort(c.opts.Addrs[0]) + var addrs []string + + // iterate the options addresses + for _, address := range c.opts.Addrs { + // check we have a port + addr, port, err := net.SplitHostPort(address) if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" { port = "8500" - addr = c.opts.Addrs[0] - config.Address = fmt.Sprintf("%s:%s", addr, port) + addr = address + addrs = append(addrs, fmt.Sprintf("%s:%s", addr, port)) } else if err == nil { - config.Address = fmt.Sprintf("%s:%s", addr, port) + addrs = append(addrs, fmt.Sprintf("%s:%s", addr, port)) } } + // set the addrs + if len(addrs) > 0 { + c.Address = addrs + config.Address = c.Address[0] + } + if config.HttpClient == nil { config.HttpClient = new(http.Client) } // requires secure connection? if c.opts.Secure || c.opts.TLSConfig != nil { - config.Scheme = "https" // We're going to support InsecureSkipVerify config.HttpClient.Transport = newTransport(c.opts.TLSConfig) @@ -122,12 +134,14 @@ func configure(c *consulRegistry, opts ...registry.Option) { config.HttpClient.Timeout = c.opts.Timeout } - // create the client - client, _ := consul.NewClient(config) + // set the config + c.config = config - // set address/client - c.Address = config.Address - c.Client = client + // remove client + c.client = nil + + // setup the client + c.Client() } func (c *consulRegistry) Init(opts ...registry.Option) error { @@ -147,7 +161,7 @@ func (c *consulRegistry) Deregister(s *registry.Service) error { c.Unlock() node := s.Nodes[0] - return c.Client.Agent().ServiceDeregister(node.Id) + return c.Client().Agent().ServiceDeregister(node.Id) } func (c *consulRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error { @@ -192,7 +206,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register if time.Since(lastChecked) <= getDeregisterTTL(regInterval) { return nil } - services, _, err := c.Client.Health().Checks(s.Name, c.queryOptions) + services, _, err := c.Client().Health().Checks(s.Name, c.queryOptions) if err == nil { for _, v := range services { if v.ServiceID == node.Id { @@ -203,7 +217,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register } else { // if the err is nil we're all good, bail out // if not, we don't know what the state is, so full re-register - if err := c.Client.Agent().PassTTL("service:"+node.Id, ""); err == nil { + if err := c.Client().Agent().PassTTL("service:"+node.Id, ""); err == nil { return nil } } @@ -220,7 +234,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register deregTTL := getDeregisterTTL(regInterval) check = &consul.AgentServiceCheck{ - TCP: fmt.Sprintf("%s:%d", node.Address, node.Port), + TCP: node.Address, Interval: fmt.Sprintf("%v", regInterval), DeregisterCriticalServiceAfter: fmt.Sprintf("%v", deregTTL), } @@ -235,13 +249,16 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register } } + host, pt, _ := net.SplitHostPort(node.Address) + port, _ := strconv.Atoi(pt) + // register the service asr := &consul.AgentServiceRegistration{ ID: node.Id, Name: s.Name, Tags: tags, - Port: node.Port, - Address: node.Address, + Port: port, + Address: host, Check: check, } @@ -252,7 +269,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register } } - if err := c.Client.Agent().ServiceRegister(asr); err != nil { + if err := c.Client().Agent().ServiceRegister(asr); err != nil { return err } @@ -268,7 +285,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register } // pass the healthcheck - return c.Client.Agent().PassTTL("service:"+node.Id, "") + return c.Client().Agent().PassTTL("service:"+node.Id, "") } func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) { @@ -277,9 +294,9 @@ func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) { // if we're connect enabled only get connect services if c.connect { - rsp, _, err = c.Client.Health().Connect(name, "", false, c.queryOptions) + rsp, _, err = c.Client().Health().Connect(name, "", false, c.queryOptions) } else { - rsp, _, err = c.Client.Health().Service(name, "", false, c.queryOptions) + rsp, _, err = c.Client().Health().Service(name, "", false, c.queryOptions) } if err != nil { return nil, err @@ -334,8 +351,7 @@ func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) { svc.Nodes = append(svc.Nodes, ®istry.Node{ Id: id, - Address: address, - Port: s.Service.Port, + Address: fmt.Sprintf("%s:%d", address, s.Service.Port), Metadata: decodeMetadata(s.Service.Tags), }) } @@ -348,7 +364,7 @@ func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) { } func (c *consulRegistry) ListServices() ([]*registry.Service, error) { - rsp, _, err := c.Client.Catalog().Services(c.queryOptions) + rsp, _, err := c.Client().Catalog().Services(c.queryOptions) if err != nil { return nil, err } @@ -374,6 +390,36 @@ func (c *consulRegistry) Options() registry.Options { return c.opts } +func (c *consulRegistry) Client() *consul.Client { + if c.client != nil { + return c.client + } + + for _, addr := range c.Address { + // set the address + c.config.Address = addr + + // create a new client + tmpClient, _ := consul.NewClient(c.config) + + // test the client + _, err := tmpClient.Agent().Host() + if err != nil { + continue + } + + // set the client + c.client = tmpClient + return c.client + } + + // set the default + c.client, _ = consul.NewClient(c.config) + + // return the client + return c.client +} + func NewRegistry(opts ...registry.Option) registry.Registry { cr := &consulRegistry{ opts: registry.Options{}, diff --git a/registry/consul/registry_test.go b/registry/consul/registry_test.go index 37eabd17..3a275030 100644 --- a/registry/consul/registry_test.go +++ b/registry/consul/registry_test.go @@ -50,22 +50,24 @@ func newConsulTestRegistry(r *mockRegistry) (*consulRegistry, func()) { } cfg := consul.DefaultConfig() cfg.Address = l.Addr().String() - cl, _ := consul.NewClient(cfg) go newMockServer(r, l) - return &consulRegistry{ - Address: cfg.Address, - Client: cl, - opts: registry.Options{}, - register: make(map[string]uint64), - lastChecked: make(map[string]time.Time), - queryOptions: &consul.QueryOptions{ - AllowStale: true, - }, - }, func() { - l.Close() - } + var cr = &consulRegistry{ + config: cfg, + Address: []string{cfg.Address}, + opts: registry.Options{}, + register: make(map[string]uint64), + lastChecked: make(map[string]time.Time), + queryOptions: &consul.QueryOptions{ + AllowStale: true, + }, + } + cr.Client() + + return cr, func() { + l.Close() + } } func newServiceList(svc []*consul.ServiceEntry) []byte { diff --git a/registry/consul/watcher.go b/registry/consul/watcher.go index 1bb9d5b7..031d02b7 100644 --- a/registry/consul/watcher.go +++ b/registry/consul/watcher.go @@ -1,6 +1,7 @@ package consul import ( + "fmt" "log" "os" "sync" @@ -44,7 +45,7 @@ func newConsulWatcher(cr *consulRegistry, opts ...registry.WatchOption) (registr } wp.Handler = cw.handle - go wp.RunWithClientAndLogger(cr.Client, log.New(os.Stderr, "", log.LstdFlags)) + go wp.RunWithClientAndLogger(cr.Client(), log.New(os.Stderr, "", log.LstdFlags)) cw.wp = wp return cw, nil @@ -102,8 +103,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { svc.Nodes = append(svc.Nodes, ®istry.Node{ Id: id, - Address: address, - Port: e.Service.Port, + Address: fmt.Sprintf("%s:%d", address, e.Service.Port), Metadata: decodeMetadata(e.Service.Tags), }) } @@ -209,7 +209,7 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) { }) if err == nil { wp.Handler = cw.serviceHandler - go wp.RunWithClientAndLogger(cw.r.Client, log.New(os.Stderr, "", log.LstdFlags)) + go wp.RunWithClientAndLogger(cw.r.Client(), log.New(os.Stderr, "", log.LstdFlags)) cw.watchers[service] = wp cw.next <- ®istry.Result{Action: "create", Service: ®istry.Service{Name: service}} } @@ -224,9 +224,14 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) { cw.RUnlock() // remove unknown services from registry + // save the things we want to delete + deleted := make(map[string][]*registry.Service) + for service, _ := range rservices { if _, ok := services[service]; !ok { cw.Lock() + // save this before deleting + deleted[service] = cw.services[service] delete(cw.services, service) cw.Unlock() } @@ -237,6 +242,11 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) { if _, ok := services[service]; !ok { w.Stop() delete(cw.watchers, service) + for _, oldService := range deleted[service] { + // send a delete for the service nodes that we're removing + cw.next <- ®istry.Result{Action: "delete", Service: oldService} + } + // sent the empty list as the last resort to indicate to delete the entire service cw.next <- ®istry.Result{Action: "delete", Service: ®istry.Service{Name: service}} } } diff --git a/registry/gossip/gossip.go b/registry/gossip/gossip.go index c8ea457a..24006438 100644 --- a/registry/gossip/gossip.go +++ b/registry/gossip/gossip.go @@ -626,7 +626,7 @@ func (g *gossipRegistry) run() { g.services[u.Service.Name] = []*registry.Service{u.Service} } else { - g.services[u.Service.Name] = addServices(service, []*registry.Service{u.Service}) + g.services[u.Service.Name] = registry.Merge(service, []*registry.Service{u.Service}) } g.Unlock() @@ -645,7 +645,7 @@ func (g *gossipRegistry) run() { case actionTypeDelete: g.Lock() if service, ok := g.services[u.Service.Name]; ok { - if services := delServices(service, []*registry.Service{u.Service}); len(services) == 0 { + if services := registry.Remove(service, []*registry.Service{u.Service}); len(services) == 0 { delete(g.services, u.Service.Name) } else { g.services[u.Service.Name] = services @@ -706,7 +706,7 @@ func (g *gossipRegistry) Register(s *registry.Service, opts ...registry.Register if service, ok := g.services[s.Name]; !ok { g.services[s.Name] = []*registry.Service{s} } else { - g.services[s.Name] = addServices(service, []*registry.Service{s}) + g.services[s.Name] = registry.Merge(service, []*registry.Service{s}) } g.Unlock() @@ -734,6 +734,12 @@ func (g *gossipRegistry) Register(s *registry.Service, opts ...registry.Register notify: nil, }) + // send update to local watchers + g.updates <- &update{ + Update: up, + Service: s, + } + // wait <-time.After(g.interval * 2) @@ -748,7 +754,7 @@ func (g *gossipRegistry) Deregister(s *registry.Service) error { g.Lock() if service, ok := g.services[s.Name]; ok { - if services := delServices(service, []*registry.Service{s}); len(services) == 0 { + if services := registry.Remove(service, []*registry.Service{s}); len(services) == 0 { delete(g.services, s.Name) } else { g.services[s.Name] = services @@ -770,6 +776,12 @@ func (g *gossipRegistry) Deregister(s *registry.Service) error { notify: nil, }) + // send update to local watchers + g.updates <- &update{ + Update: up, + Service: s, + } + // wait <-time.After(g.interval * 2) diff --git a/registry/gossip/util.go b/registry/gossip/util.go deleted file mode 100644 index 0fa8116d..00000000 --- a/registry/gossip/util.go +++ /dev/null @@ -1,141 +0,0 @@ -package gossip - -import ( - "github.com/micro/go-micro/registry" -) - -func cp(current []*registry.Service) []*registry.Service { - var services []*registry.Service - - for _, service := range current { - // copy service - s := new(registry.Service) - *s = *service - - // copy nodes - var nodes []*registry.Node - for _, node := range service.Nodes { - n := new(registry.Node) - *n = *node - nodes = append(nodes, n) - } - s.Nodes = nodes - - // copy endpoints - var eps []*registry.Endpoint - for _, ep := range service.Endpoints { - e := new(registry.Endpoint) - *e = *ep - eps = append(eps, e) - } - s.Endpoints = eps - - // append service - services = append(services, s) - } - - return services -} - -func addNodes(old, neu []*registry.Node) []*registry.Node { - var nodes []*registry.Node - - // add all new nodes - for _, n := range neu { - node := *n - nodes = append(nodes, &node) - } - - // look at old nodes - for _, o := range old { - var exists bool - - // check against new nodes - for _, n := range nodes { - // ids match then skip - if o.Id == n.Id { - exists = true - break - } - } - - // keep old node - if !exists { - node := *o - nodes = append(nodes, &node) - } - } - - return nodes -} - -func addServices(old, neu []*registry.Service) []*registry.Service { - var srv []*registry.Service - - for _, s := range neu { - var seen bool - for _, o := range old { - if o.Version == s.Version { - sp := new(registry.Service) - // make copy - *sp = *o - // set nodes - sp.Nodes = addNodes(o.Nodes, s.Nodes) - - // mark as seen - seen = true - srv = append(srv, sp) - break - } - } - if !seen { - srv = append(srv, cp([]*registry.Service{s})...) - } - } - - return srv -} - -func delNodes(old, del []*registry.Node) []*registry.Node { - var nodes []*registry.Node - for _, o := range old { - var rem bool - for _, n := range del { - if o.Id == n.Id { - rem = true - break - } - } - if !rem { - nodes = append(nodes, o) - } - } - return nodes -} - -func delServices(old, del []*registry.Service) []*registry.Service { - var services []*registry.Service - - for _, o := range old { - srv := new(registry.Service) - *srv = *o - - var rem bool - - for _, s := range del { - if srv.Version == s.Version { - srv.Nodes = delNodes(srv.Nodes, s.Nodes) - - if len(srv.Nodes) == 0 { - rem = true - } - } - } - - if !rem { - services = append(services, srv) - } - } - - return services -} diff --git a/registry/mdns_registry.go b/registry/mdns_registry.go index 4a3d09ec..8e68948a 100644 --- a/registry/mdns_registry.go +++ b/registry/mdns_registry.go @@ -3,7 +3,9 @@ package registry import ( "context" + "fmt" "net" + "strconv" "strings" "sync" "time" @@ -127,14 +129,22 @@ func (m *mdnsRegistry) Register(service *Service, opts ...RegisterOption) error continue } + // + host, pt, err := net.SplitHostPort(node.Address) + if err != nil { + gerr = err + continue + } + port, _ := strconv.Atoi(pt) + // we got here, new node s, err := mdns.NewMDNSService( node.Id, service.Name, "", "", - node.Port, - []net.IP{net.ParseIP(node.Address)}, + port, + []net.IP{net.ParseIP(host)}, txt, ) if err != nil { @@ -238,8 +248,7 @@ func (m *mdnsRegistry) GetService(service string) ([]*Service, error) { s.Nodes = append(s.Nodes, &Node{ Id: strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+"."), - Address: e.AddrV4.String(), - Port: e.Port, + Address: fmt.Sprintf("%s:%d", e.AddrV4.String(), e.Port), Metadata: txt.Metadata, }) diff --git a/registry/mdns_test.go b/registry/mdns_test.go index 5690fe39..e9813c69 100644 --- a/registry/mdns_test.go +++ b/registry/mdns_test.go @@ -13,8 +13,7 @@ func TestMDNS(t *testing.T) { Nodes: []*Node{ &Node{ Id: "test1-1", - Address: "10.0.0.1", - Port: 10001, + Address: "10.0.0.1:10001", Metadata: map[string]string{ "foo": "bar", }, @@ -27,8 +26,7 @@ func TestMDNS(t *testing.T) { Nodes: []*Node{ &Node{ Id: "test2-1", - Address: "10.0.0.2", - Port: 10002, + Address: "10.0.0.2:10002", Metadata: map[string]string{ "foo2": "bar2", }, @@ -41,8 +39,7 @@ func TestMDNS(t *testing.T) { Nodes: []*Node{ &Node{ Id: "test3-1", - Address: "10.0.0.3", - Port: 10003, + Address: "10.0.0.3:10003", Metadata: map[string]string{ "foo3": "bar3", }, @@ -92,10 +89,6 @@ func TestMDNS(t *testing.T) { if node.Address != service.Nodes[0].Address { t.Fatalf("Expected node address %s got %s", service.Nodes[0].Address, node.Address) } - - if node.Port != service.Nodes[0].Port { - t.Fatalf("Expected node port %d got %d", service.Nodes[0].Port, node.Port) - } } services, err := r.ListServices() diff --git a/registry/mdns_watcher.go b/registry/mdns_watcher.go index 7ccb6e80..e1c326ff 100644 --- a/registry/mdns_watcher.go +++ b/registry/mdns_watcher.go @@ -1,7 +1,7 @@ package registry import ( - "errors" + "fmt" "strings" "github.com/micro/mdns" @@ -53,8 +53,7 @@ func (m *mdnsWatcher) Next() (*Result, error) { service.Nodes = append(service.Nodes, &Node{ Id: strings.TrimSuffix(e.Name, "."+service.Name+".local."), - Address: e.AddrV4.String(), - Port: e.Port, + Address: fmt.Sprintf("%s:%d", e.AddrV4.String(), e.Port), Metadata: txt.Metadata, }) @@ -63,7 +62,7 @@ func (m *mdnsWatcher) Next() (*Result, error) { Service: service, }, nil case <-m.exit: - return nil, errors.New("watcher stopped") + return nil, ErrWatcherStopped } } } diff --git a/registry/memory/helper.go b/registry/memory/helper.go deleted file mode 100644 index 2308dde7..00000000 --- a/registry/memory/helper.go +++ /dev/null @@ -1,76 +0,0 @@ -package memory - -import ( - "github.com/micro/go-micro/registry" -) - -func addNodes(old, neu []*registry.Node) []*registry.Node { - for _, n := range neu { - var seen bool - for i, o := range old { - if o.Id == n.Id { - seen = true - old[i] = n - break - } - } - if !seen { - old = append(old, n) - } - } - return old -} - -func addServices(old, neu []*registry.Service) []*registry.Service { - for _, s := range neu { - var seen bool - for i, o := range old { - if o.Version == s.Version { - s.Nodes = addNodes(o.Nodes, s.Nodes) - seen = true - old[i] = s - break - } - } - if !seen { - old = append(old, s) - } - } - return old -} - -func delNodes(old, del []*registry.Node) []*registry.Node { - var nodes []*registry.Node - for _, o := range old { - var rem bool - for _, n := range del { - if o.Id == n.Id { - rem = true - break - } - } - if !rem { - nodes = append(nodes, o) - } - } - return nodes -} - -func delServices(old, del []*registry.Service) []*registry.Service { - var services []*registry.Service - for i, o := range old { - var rem bool - for _, s := range del { - if o.Version == s.Version { - old[i].Nodes = delNodes(o.Nodes, s.Nodes) - if len(old[i].Nodes) == 0 { - rem = true - } - } - } - if !rem { - services = append(services, o) - } - } - return services -} diff --git a/registry/memory/helper_test.go b/registry/memory/helper_test.go deleted file mode 100644 index f19d1c52..00000000 --- a/registry/memory/helper_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package memory - -import ( - "testing" - - "github.com/micro/go-micro/registry" -) - -func TestDelServices(t *testing.T) { - services := []*registry.Service{ - { - Name: "foo", - Version: "1.0.0", - Nodes: []*registry.Node{ - { - Id: "foo-123", - Address: "localhost", - Port: 9999, - }, - }, - }, - { - Name: "foo", - Version: "1.0.0", - Nodes: []*registry.Node{ - { - Id: "foo-123", - Address: "localhost", - Port: 6666, - }, - }, - }, - } - - servs := delServices([]*registry.Service{services[0]}, []*registry.Service{services[1]}) - if i := len(servs); i > 0 { - t.Errorf("Expected 0 nodes, got %d: %+v", i, servs) - } - t.Logf("Services %+v", servs) -} - -func TestDelNodes(t *testing.T) { - services := []*registry.Service{ - { - Name: "foo", - Version: "1.0.0", - Nodes: []*registry.Node{ - { - Id: "foo-123", - Address: "localhost", - Port: 9999, - }, - { - Id: "foo-321", - Address: "localhost", - Port: 6666, - }, - }, - }, - { - Name: "foo", - Version: "1.0.0", - Nodes: []*registry.Node{ - { - Id: "foo-123", - Address: "localhost", - Port: 6666, - }, - }, - }, - } - - nodes := delNodes(services[0].Nodes, services[1].Nodes) - if i := len(nodes); i != 1 { - t.Errorf("Expected only 1 node, got %d: %+v", i, nodes) - } - t.Logf("Nodes %+v", nodes) -} diff --git a/registry/memory/memory.go b/registry/memory/memory.go index e159ce61..6c03df67 100644 --- a/registry/memory/memory.go +++ b/registry/memory/memory.go @@ -22,17 +22,6 @@ var ( timeout = time.Millisecond * 10 ) -/* -// Setup sets mock data -func (m *Registry) Setup() { - m.Lock() - defer m.Unlock() - - // add some memory data - m.Services = Data -} -*/ - func (m *Registry) watch(r *registry.Result) { var watchers []*Watcher @@ -66,7 +55,7 @@ func (m *Registry) Init(opts ...registry.Option) error { m.Lock() for k, v := range getServices(m.options.Context) { s := m.Services[k] - m.Services[k] = addServices(s, v) + m.Services[k] = registry.Merge(s, v) } m.Unlock() return nil @@ -76,20 +65,20 @@ func (m *Registry) Options() registry.Options { return m.options } -func (m *Registry) GetService(service string) ([]*registry.Service, error) { +func (m *Registry) GetService(name string) ([]*registry.Service, error) { m.RLock() - s, ok := m.Services[service] - if !ok || len(s) == 0 { - m.RUnlock() + service, ok := m.Services[name] + m.RUnlock() + if !ok { return nil, registry.ErrNotFound } - m.RUnlock() - return s, nil + + return service, nil } func (m *Registry) ListServices() ([]*registry.Service, error) { - m.RLock() var services []*registry.Service + m.RLock() for _, service := range m.Services { services = append(services, service...) } @@ -99,11 +88,14 @@ func (m *Registry) ListServices() ([]*registry.Service, error) { func (m *Registry) Register(s *registry.Service, opts ...registry.RegisterOption) error { go m.watch(®istry.Result{Action: "update", Service: s}) - m.Lock() - services := addServices(m.Services[s.Name], []*registry.Service{s}) - m.Services[s.Name] = services + if service, ok := m.Services[s.Name]; !ok { + m.Services[s.Name] = []*registry.Service{s} + } else { + m.Services[s.Name] = registry.Merge(service, []*registry.Service{s}) + } m.Unlock() + return nil } @@ -111,9 +103,15 @@ func (m *Registry) Deregister(s *registry.Service) error { go m.watch(®istry.Result{Action: "delete", Service: s}) m.Lock() - services := delServices(m.Services[s.Name], []*registry.Service{s}) - m.Services[s.Name] = services + if service, ok := m.Services[s.Name]; ok { + if service := registry.Remove(service, []*registry.Service{s}); len(service) == 0 { + delete(m.Services, s.Name) + } else { + m.Services[s.Name] = service + } + } m.Unlock() + return nil } diff --git a/registry/memory/memory_test.go b/registry/memory/memory_test.go index 3b5cd8be..fa8b53f1 100644 --- a/registry/memory/memory_test.go +++ b/registry/memory/memory_test.go @@ -15,13 +15,11 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.0-123", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, { Id: "foo-1.0.0-321", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, }, }, @@ -31,8 +29,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.1-321", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, @@ -42,8 +39,7 @@ var ( Nodes: []*registry.Node{ { Id: "foo-1.0.3-345", - Address: "localhost", - Port: 8888, + Address: "localhost:8888", }, }, }, @@ -55,13 +51,11 @@ var ( Nodes: []*registry.Node{ { Id: "bar-1.0.0-123", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, { Id: "bar-1.0.0-321", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, }, }, @@ -71,8 +65,7 @@ var ( Nodes: []*registry.Node{ { Id: "bar-1.0.1-321", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, diff --git a/registry/service.go b/registry/service.go index 5e13a002..5259a6b6 100644 --- a/registry/service.go +++ b/registry/service.go @@ -11,7 +11,6 @@ type Service struct { type Node struct { Id string `json:"id"` Address string `json:"address"` - Port int `json:"port"` Metadata map[string]string `json:"metadata"` } diff --git a/registry/util.go b/registry/util.go new file mode 100644 index 00000000..5cd04eb9 --- /dev/null +++ b/registry/util.go @@ -0,0 +1,142 @@ +package registry + +func addNodes(old, neu []*Node) []*Node { + nodes := make([]*Node, len(neu)) + // add all new nodes + for i, n := range neu { + node := *n + nodes[i] = &node + } + + // look at old nodes + for _, o := range old { + var exists bool + + // check against new nodes + for _, n := range nodes { + // ids match then skip + if o.Id == n.Id { + exists = true + break + } + } + + // keep old node + if !exists { + node := *o + nodes = append(nodes, &node) + } + } + + return nodes +} + +func delNodes(old, del []*Node) []*Node { + var nodes []*Node + for _, o := range old { + var rem bool + for _, n := range del { + if o.Id == n.Id { + rem = true + break + } + } + if !rem { + nodes = append(nodes, o) + } + } + return nodes +} + +// Copy makes a copy of services +func Copy(current []*Service) []*Service { + services := make([]*Service, len(current)) + for i, service := range current { + // copy service + s := new(Service) + *s = *service + + // copy nodes + nodes := make([]*Node, len(service.Nodes)) + for j, node := range service.Nodes { + n := new(Node) + *n = *node + nodes[j] = n + } + s.Nodes = nodes + + // copy endpoints + eps := make([]*Endpoint, len(service.Endpoints)) + for j, ep := range service.Endpoints { + e := new(Endpoint) + *e = *ep + eps[j] = e + } + s.Endpoints = eps + + // append service + services[i] = s + } + + return services +} + +// Merge merges two lists of services and returns a new copy +func Merge(olist []*Service, nlist []*Service) []*Service { + var srv []*Service + + for _, n := range nlist { + var seen bool + for _, o := range olist { + if o.Version == n.Version { + sp := new(Service) + // make copy + *sp = *o + // set nodes + sp.Nodes = addNodes(o.Nodes, n.Nodes) + + // mark as seen + seen = true + srv = append(srv, sp) + break + } else { + sp := new(Service) + // make copy + *sp = *o + srv = append(srv, sp) + } + } + if !seen { + srv = append(srv, Copy([]*Service{n})...) + } + } + return srv +} + +// Remove removes services and returns a new copy +func Remove(old, del []*Service) []*Service { + var services []*Service + + for _, o := range old { + srv := new(Service) + *srv = *o + + var rem bool + + for _, s := range del { + if srv.Version == s.Version { + srv.Nodes = delNodes(srv.Nodes, s.Nodes) + + if len(srv.Nodes) == 0 { + rem = true + } + } + } + + if !rem { + services = append(services, srv) + } + } + + return services +} diff --git a/registry/gossip/util_test.go b/registry/util_test.go similarity index 54% rename from registry/gossip/util_test.go rename to registry/util_test.go index a77dda52..75fd5133 100644 --- a/registry/gossip/util_test.go +++ b/registry/util_test.go @@ -1,70 +1,63 @@ -package gossip +package registry import ( "testing" - - "github.com/micro/go-micro/registry" ) -func TestDelServices(t *testing.T) { - services := []*registry.Service{ +func TestRemove(t *testing.T) { + services := []*Service{ { Name: "foo", Version: "1.0.0", - Nodes: []*registry.Node{ + Nodes: []*Node{ { Id: "foo-123", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, }, }, { Name: "foo", Version: "1.0.0", - Nodes: []*registry.Node{ + Nodes: []*Node{ { Id: "foo-123", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, } - servs := delServices([]*registry.Service{services[0]}, []*registry.Service{services[1]}) + servs := Remove([]*Service{services[0]}, []*Service{services[1]}) if i := len(servs); i > 0 { t.Errorf("Expected 0 nodes, got %d: %+v", i, servs) } t.Logf("Services %+v", servs) } -func TestDelNodes(t *testing.T) { - services := []*registry.Service{ +func TestRemoveNodes(t *testing.T) { + services := []*Service{ { Name: "foo", Version: "1.0.0", - Nodes: []*registry.Node{ + Nodes: []*Node{ { Id: "foo-123", - Address: "localhost", - Port: 9999, + Address: "localhost:9999", }, { Id: "foo-321", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, { Name: "foo", Version: "1.0.0", - Nodes: []*registry.Node{ + Nodes: []*Node{ { Id: "foo-123", - Address: "localhost", - Port: 6666, + Address: "localhost:6666", }, }, }, diff --git a/registry/watcher_test.go b/registry/watcher_test.go index 41b606b2..94c8c275 100644 --- a/registry/watcher_test.go +++ b/registry/watcher_test.go @@ -12,8 +12,7 @@ func TestWatcher(t *testing.T) { Nodes: []*Node{ &Node{ Id: "test1-1", - Address: "10.0.0.1", - Port: 10001, + Address: "10.0.0.1:10001", Metadata: map[string]string{ "foo": "bar", }, @@ -26,8 +25,7 @@ func TestWatcher(t *testing.T) { Nodes: []*Node{ &Node{ Id: "test2-1", - Address: "10.0.0.2", - Port: 10002, + Address: "10.0.0.2:10002", Metadata: map[string]string{ "foo2": "bar2", }, @@ -40,8 +38,7 @@ func TestWatcher(t *testing.T) { Nodes: []*Node{ &Node{ Id: "test3-1", - Address: "10.0.0.3", - Port: 10003, + Address: "10.0.0.3:10003", Metadata: map[string]string{ "foo3": "bar3", }, @@ -77,10 +74,6 @@ func TestWatcher(t *testing.T) { if node.Address != service.Nodes[0].Address { t.Fatalf("Expected node address %s got %s", service.Nodes[0].Address, node.Address) } - - if node.Port != service.Nodes[0].Port { - t.Fatalf("Expected node port %d got %d", service.Nodes[0].Port, node.Port) - } } // new registry diff --git a/router/default.go b/router/default.go new file mode 100644 index 00000000..498539a1 --- /dev/null +++ b/router/default.go @@ -0,0 +1,713 @@ +package router + +import ( + "fmt" + "math" + "sort" + "strings" + "sync" + "time" + + "github.com/google/uuid" + "github.com/micro/go-micro/registry" +) + +const ( + // AdvertiseEventsTick is time interval in which the router advertises route updates + AdvertiseEventsTick = 5 * time.Second + // AdvertiseTableTick is time interval in which router advertises all routes found in routing table + AdvertiseTableTick = 1 * time.Minute + // AdvertiseFlushTick is time the yet unconsumed advertisements are flush i.e. discarded + AdvertiseFlushTick = 15 * time.Second + // AdvertSuppress is advert suppression threshold + AdvertSuppress = 2000.0 + // AdvertRecover is advert recovery threshold + AdvertRecover = 750.0 + // DefaultAdvertTTL is default advertisement TTL + DefaultAdvertTTL = 1 * time.Minute + // DeletePenalty penalises route deletion + DeletePenalty = 1000.0 + // UpdatePenalty penalises route updates + UpdatePenalty = 500.0 + // PenaltyHalfLife is the time the advert penalty decays to half its value + PenaltyHalfLife = 2.0 + // MaxSuppressTime defines time after which the suppressed advert is deleted + MaxSuppressTime = 5 * time.Minute +) + +var ( + // PenaltyDecay is a coefficient which controls the speed the advert penalty decays + PenaltyDecay = math.Log(2) / PenaltyHalfLife +) + +// router implements default router +type router struct { + sync.RWMutex + // embed the table + table *table + opts Options + status Status + exit chan struct{} + errChan chan error + eventChan chan *Event + advertWg *sync.WaitGroup + wg *sync.WaitGroup + + // advert subscribers + subscribers map[string]chan *Advert +} + +// newRouter creates new router and returns it +func newRouter(opts ...Option) Router { + // get default options + options := DefaultOptions() + + // apply requested options + for _, o := range opts { + o(&options) + } + + r := &router{ + table: newTable(), + opts: options, + status: Status{Code: Stopped, Error: nil}, + advertWg: &sync.WaitGroup{}, + wg: &sync.WaitGroup{}, + subscribers: make(map[string]chan *Advert), + } + + go r.run() + + return r +} + +// Init initializes router with given options +func (r *router) Init(opts ...Option) error { + for _, o := range opts { + o(&r.opts) + } + return nil +} + +// Options returns router options +func (r *router) Options() Options { + return r.opts +} + +func (r *router) Table() Table { + return r.table +} + +// manageRoute applies action on a given route +func (r *router) manageRoute(route Route, action string) error { + switch action { + case "create": + if err := r.table.Create(route); err != nil && err != ErrDuplicateRoute { + return fmt.Errorf("failed adding route for service %s: %s", route.Service, err) + } + case "update": + if err := r.table.Update(route); err != nil && err != ErrDuplicateRoute { + return fmt.Errorf("failed updating route for service %s: %s", route.Service, err) + } + case "delete": + if err := r.table.Delete(route); err != nil && err != ErrRouteNotFound { + return fmt.Errorf("failed deleting route for service %s: %s", route.Service, err) + } + default: + return fmt.Errorf("failed to manage route for service %s. Unknown action: %s", route.Service, action) + } + + return nil +} + +// manageServiceRoutes applies action to all routes of the service. +// It returns error of the action fails with error. +func (r *router) manageServiceRoutes(service *registry.Service, action string) error { + // action is the routing table action + action = strings.ToLower(action) + + // take route action on each service node + for _, node := range service.Nodes { + route := Route{ + Service: service.Name, + Address: node.Address, + Gateway: "", + Network: r.opts.Network, + Link: DefaultLink, + Metric: DefaultLocalMetric, + } + + if err := r.manageRoute(route, action); err != nil { + return err + } + } + + return nil +} + +// manageRegistryRoutes applies action to all routes of each service found in the registry. +// It returns error if either the services failed to be listed or the routing table action fails. +func (r *router) manageRegistryRoutes(reg registry.Registry, action string) error { + services, err := reg.ListServices() + if err != nil { + return fmt.Errorf("failed listing services: %v", err) + } + + // add each service node as a separate route + for _, service := range services { + // get the service to retrieve all its info + srvs, err := reg.GetService(service.Name) + if err != nil { + continue + } + // manage the routes for all returned services + for _, srv := range srvs { + if err := r.manageServiceRoutes(srv, action); err != nil { + return err + } + } + } + + return nil +} + +// watchRegistry watches registry and updates routing table based on the received events. +// It returns error if either the registry watcher fails with error or if the routing table update fails. +func (r *router) watchRegistry(w registry.Watcher) error { + // wait in the background for the router to stop + // when the router stops, stop the watcher and exit + r.wg.Add(1) + + exit := make(chan bool) + + defer func() { + // close the exit channel when the go routine finishes + close(exit) + r.wg.Done() + }() + + go func() { + defer w.Stop() + + select { + case <-r.exit: + return + case <-exit: + return + } + }() + + var watchErr error + + for { + res, err := w.Next() + if err != nil { + if err != registry.ErrWatcherStopped { + watchErr = err + } + break + } + + if err := r.manageServiceRoutes(res.Service, res.Action); err != nil { + return err + } + } + + return watchErr +} + +// watchTable watches routing table entries and either adds or deletes locally registered service to/from network registry +// It returns error if the locally registered services either fails to be added/deleted to/from network registry. +func (r *router) watchTable(w Watcher) error { + // wait in the background for the router to stop + // when the router stops, stop the watcher and exit + r.wg.Add(1) + exit := make(chan bool) + + defer func() { + // close the exit channel when the go routine finishes + close(exit) + r.wg.Done() + }() + + go func() { + defer w.Stop() + + select { + case <-r.exit: + return + case <-exit: + return + } + }() + + var watchErr error + + for { + event, err := w.Next() + if err != nil { + if err != ErrWatcherStopped { + watchErr = err + } + break + } + + select { + case <-r.exit: + close(r.eventChan) + return nil + case r.eventChan <- event: + } + } + + // close event channel on error + close(r.eventChan) + + return watchErr +} + +// publishAdvert publishes router advert to advert channel +// NOTE: this might cease to be a dedicated method in the future +func (r *router) publishAdvert(advType AdvertType, events []*Event) { + defer r.advertWg.Done() + + a := &Advert{ + Id: r.opts.Id, + Type: advType, + TTL: DefaultAdvertTTL, + Timestamp: time.Now(), + Events: events, + } + + r.RLock() + for _, sub := range r.subscribers { + // check the exit chan first + select { + case <-r.exit: + r.RUnlock() + return + default: + } + + // now send the message + select { + case sub <- a: + default: + } + } + r.RUnlock() +} + +// advertiseTable advertises the whole routing table to the network +func (r *router) advertiseTable() error { + // create table advertisement ticker + ticker := time.NewTicker(AdvertiseTableTick) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + // list routing table routes to announce + routes, err := r.table.List() + if err != nil { + return fmt.Errorf("failed listing routes: %s", err) + } + // collect all the added routes before we attempt to add default gateway + events := make([]*Event, len(routes)) + for i, route := range routes { + event := &Event{ + Type: Update, + Timestamp: time.Now(), + Route: route, + } + events[i] = event + } + + // advertise all routes as Update events to subscribers + if len(events) > 0 { + r.advertWg.Add(1) + go r.publishAdvert(RouteUpdate, events) + } + case <-r.exit: + return nil + } + } +} + +// routeAdvert contains a list of route events to be advertised +type routeAdvert struct { + events []*Event + // lastUpdate records the time of the last advert update + lastUpdate time.Time + // penalty is current advert penalty + penalty float64 + // isSuppressed flags the advert suppression + isSuppressed bool + // suppressTime records the time interval the advert has been suppressed for + suppressTime time.Time +} + +// advertiseEvents advertises routing table events +// It suppresses unhealthy flapping events and advertises healthy events upstream. +func (r *router) advertiseEvents() error { + // ticker to periodically scan event for advertising + ticker := time.NewTicker(AdvertiseEventsTick) + defer ticker.Stop() + + // advertMap is a map of advert events + advertMap := make(map[uint64]*routeAdvert) + + // routing table watcher + tableWatcher, err := r.Watch() + if err != nil { + return fmt.Errorf("failed creating routing table watcher: %v", err) + } + + r.wg.Add(1) + go func() { + defer r.wg.Done() + select { + case r.errChan <- r.watchTable(tableWatcher): + case <-r.exit: + } + }() + + for { + select { + case <-ticker.C: + var events []*Event + // collect all events which are not flapping + for key, advert := range advertMap { + // decay the event penalty + delta := time.Since(advert.lastUpdate).Seconds() + advert.penalty = advert.penalty * math.Exp(-delta*PenaltyDecay) + + // suppress/recover the event based on its penalty level + switch { + case advert.penalty > AdvertSuppress && !advert.isSuppressed: + advert.isSuppressed = true + advert.suppressTime = time.Now() + case advert.penalty < AdvertRecover && advert.isSuppressed: + advert.isSuppressed = false + } + + // max suppression time threshold has been reached, delete the advert + if advert.isSuppressed { + if time.Since(advert.suppressTime) > MaxSuppressTime { + delete(advertMap, key) + continue + } + } + + if !advert.isSuppressed { + for _, event := range advert.events { + e := new(Event) + *e = *event + events = append(events, e) + // delete the advert from the advertMap + delete(advertMap, key) + } + } + } + + // advertise all Update events to subscribers + if len(events) > 0 { + r.advertWg.Add(1) + go r.publishAdvert(RouteUpdate, events) + } + case e := <-r.eventChan: + // if event is nil, continue + if e == nil { + continue + } + + // determine the event penalty + var penalty float64 + switch e.Type { + case Update: + penalty = UpdatePenalty + case Delete: + penalty = DeletePenalty + } + + // check if we have already registered the route + // we use the route hash as advertMap key + hash := e.Route.Hash() + advert, ok := advertMap[hash] + if !ok { + events := []*Event{e} + advert = &routeAdvert{ + events: events, + penalty: penalty, + lastUpdate: time.Now(), + } + advertMap[hash] = advert + continue + } + + // attempt to squash last two events if possible + lastEvent := advert.events[len(advert.events)-1] + if lastEvent.Type == e.Type { + advert.events[len(advert.events)-1] = e + } else { + advert.events = append(advert.events, e) + } + + // update event penalty and recorded timestamp + advert.lastUpdate = time.Now() + advert.penalty += penalty + case <-r.exit: + // first wait for the advertiser to finish + r.advertWg.Wait() + return nil + } + } +} + +// watchErrors watches router errors and takes appropriate actions +func (r *router) watchErrors() { + var err error + + select { + case <-r.exit: + case err = <-r.errChan: + } + + r.Lock() + defer r.Unlock() + if r.status.Code != Stopped { + // notify all goroutines to finish + close(r.exit) + + // drain the advertise channel only if advertising + if r.status.Code == Advertising { + // drain the event channel + for range r.eventChan { + } + } + + // mark the router as Stopped and set its Error to nil + r.status = Status{Code: Stopped, Error: nil} + } + + if err != nil { + r.status = Status{Code: Error, Error: err} + } +} + +// Run runs the router. +func (r *router) run() { + r.Lock() + defer r.Unlock() + + switch r.status.Code { + case Stopped, Error: + // add all local service routes into the routing table + if err := r.manageRegistryRoutes(r.opts.Registry, "create"); err != nil { + r.status = Status{Code: Error, Error: fmt.Errorf("failed adding registry routes: %s", err)} + return + } + + // add default gateway into routing table + if r.opts.Gateway != "" { + // note, the only non-default value is the gateway + route := Route{ + Service: "*", + Address: "*", + Gateway: r.opts.Gateway, + Network: "*", + Metric: DefaultLocalMetric, + } + if err := r.table.Create(route); err != nil { + r.status = Status{Code: Error, Error: fmt.Errorf("failed adding default gateway route: %s", err)} + return + } + } + + // create error and exit channels + r.errChan = make(chan error, 1) + r.exit = make(chan struct{}) + + // registry watcher + regWatcher, err := r.opts.Registry.Watch() + if err != nil { + r.status = Status{Code: Error, Error: fmt.Errorf("failed creating registry watcher: %v", err)} + return + } + + r.wg.Add(1) + go func() { + defer r.wg.Done() + select { + case r.errChan <- r.watchRegistry(regWatcher): + case <-r.exit: + } + }() + + // watch for errors and cleanup + r.wg.Add(1) + go func() { + defer r.wg.Done() + r.watchErrors() + }() + + // mark router as Running and set its Error to nil + r.status = Status{Code: Running, Error: nil} + + return + } + + return +} + +// Advertise stars advertising the routes to the network and returns the advertisements channel to consume from. +// If the router is already advertising it returns the channel to consume from. +// It returns error if either the router is not running or if the routing table fails to list the routes to advertise. +func (r *router) Advertise() (<-chan *Advert, error) { + r.Lock() + defer r.Unlock() + + switch r.status.Code { + case Advertising: + advertChan := make(chan *Advert) + r.subscribers[uuid.New().String()] = advertChan + return advertChan, nil + case Running: + // list routing table routes to announce + routes, err := r.table.List() + if err != nil { + return nil, fmt.Errorf("failed listing routes: %s", err) + } + // collect all the added routes before we attempt to add default gateway + events := make([]*Event, len(routes)) + for i, route := range routes { + event := &Event{ + Type: Create, + Timestamp: time.Now(), + Route: route, + } + events[i] = event + } + + // create event channels + r.eventChan = make(chan *Event) + + // advertise your presence + r.advertWg.Add(1) + go r.publishAdvert(Announce, events) + + r.wg.Add(1) + go func() { + defer r.wg.Done() + select { + case r.errChan <- r.advertiseEvents(): + case <-r.exit: + } + }() + + r.advertWg.Add(1) + go func() { + defer r.advertWg.Done() + // advertise the whole routing table + select { + case r.errChan <- r.advertiseTable(): + case <-r.exit: + } + }() + + // mark router as Running and set its Error to nil + r.status = Status{Code: Advertising, Error: nil} + + // create advert channel + advertChan := make(chan *Advert) + r.subscribers[uuid.New().String()] = advertChan + + return advertChan, nil + case Stopped: + return nil, fmt.Errorf("not running") + } + + return nil, fmt.Errorf("error: %s", r.status.Error) +} + +// Process updates the routing table using the advertised values +func (r *router) Process(a *Advert) error { + // NOTE: event sorting might not be necessary + // copy update events intp new slices + events := make([]*Event, len(a.Events)) + copy(events, a.Events) + // sort events by timestamp + sort.Slice(events, func(i, j int) bool { + return events[i].Timestamp.Before(events[j].Timestamp) + }) + + for _, event := range events { + // create a copy of the route + route := event.Route + action := event.Type + if err := r.manageRoute(route, fmt.Sprintf("%s", action)); err != nil { + return fmt.Errorf("failed applying action %s to routing table: %s", action, err) + } + } + + return nil +} + +func (r *router) Lookup(q Query) ([]Route, error) { + return r.table.Query(q) +} + +func (r *router) Watch(opts ...WatchOption) (Watcher, error) { + return r.table.Watch(opts...) +} + +// Status returns router status +func (r *router) Status() Status { + r.RLock() + defer r.RUnlock() + + // make a copy of the status + status := r.status + + return status +} + +// Stop stops the router +func (r *router) Stop() error { + r.Lock() + // only close the channel if the router is running and/or advertising + if r.status.Code == Running || r.status.Code == Advertising { + // notify all goroutines to finish + close(r.exit) + + // drain the advertise channel only if advertising + if r.status.Code == Advertising { + // drain the event channel + for range r.eventChan { + } + } + + // close advert subscribers + for id, sub := range r.subscribers { + // close the channel + close(sub) + + // delete the subscriber + delete(r.subscribers, id) + } + + // mark the router as Stopped and set its Error to nil + r.status = Status{Code: Stopped, Error: nil} + } + r.Unlock() + + // wait for all goroutines to finish + r.wg.Wait() + + return nil +} + +// String prints debugging information about router +func (r *router) String() string { + return "default" +} diff --git a/router/handler/router.go b/router/handler/router.go new file mode 100644 index 00000000..a7571024 --- /dev/null +++ b/router/handler/router.go @@ -0,0 +1,180 @@ +package handler + +import ( + "context" + "io" + "time" + + "github.com/micro/go-micro/errors" + "github.com/micro/go-micro/router" + pb "github.com/micro/go-micro/router/proto" +) + +// Router implements router handler +type Router struct { + Router router.Router +} + +// Lookup looks up routes in the routing table and returns them +func (r *Router) Lookup(ctx context.Context, req *pb.LookupRequest, resp *pb.LookupResponse) error { + query := router.NewQuery( + router.QueryService(req.Query.Service), + ) + + routes, err := r.Router.Lookup(query) + if err != nil { + return errors.InternalServerError("go.micro.router", "failed to lookup routes: %v", err) + } + + var respRoutes []*pb.Route + for _, route := range routes { + respRoute := &pb.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int64(route.Metric), + } + respRoutes = append(respRoutes, respRoute) + } + + resp.Routes = respRoutes + + return nil +} + +func (r *Router) Advertise(ctx context.Context, req *pb.Request, stream pb.Router_AdvertiseStream) error { + advertChan, err := r.Router.Advertise() + if err != nil { + return errors.InternalServerError("go.micro.router", "failed to get adverts: %v", err) + } + + for advert := range advertChan { + var events []*pb.Event + for _, event := range advert.Events { + route := &pb.Route{ + Service: event.Route.Service, + Address: event.Route.Address, + Gateway: event.Route.Gateway, + Network: event.Route.Network, + Link: event.Route.Link, + Metric: int64(event.Route.Metric), + } + e := &pb.Event{ + Type: pb.EventType(event.Type), + Timestamp: event.Timestamp.UnixNano(), + Route: route, + } + events = append(events, e) + } + + advert := &pb.Advert{ + Id: advert.Id, + Type: pb.AdvertType(advert.Type), + Timestamp: advert.Timestamp.UnixNano(), + Events: events, + } + + // send the advert + err := stream.Send(advert) + if err == io.EOF { + return nil + } + if err != nil { + return errors.InternalServerError("go.micro.router", "error sending message %v", err) + } + } + + return nil +} + +func (r *Router) Process(ctx context.Context, req *pb.Advert, rsp *pb.ProcessResponse) error { + events := make([]*router.Event, len(req.Events)) + for i, event := range req.Events { + route := router.Route{ + Service: event.Route.Service, + Address: event.Route.Address, + Gateway: event.Route.Gateway, + Network: event.Route.Network, + Link: event.Route.Link, + Metric: int(event.Route.Metric), + } + + events[i] = &router.Event{ + Type: router.EventType(event.Type), + Timestamp: time.Unix(0, event.Timestamp), + Route: route, + } + } + + advert := &router.Advert{ + Id: req.Id, + Type: router.AdvertType(req.Type), + Timestamp: time.Unix(0, req.Timestamp), + TTL: time.Duration(req.Ttl), + Events: events, + } + + if err := r.Router.Process(advert); err != nil { + return errors.InternalServerError("go.micro.router", "error publishing advert: %v", err) + } + + return nil +} + +func (r *Router) Status(ctx context.Context, req *pb.Request, rsp *pb.StatusResponse) error { + status := r.Router.Status() + + rsp.Status = &pb.Status{ + Code: status.Code.String(), + } + + if status.Error != nil { + rsp.Status.Error = status.Error.Error() + } + + return nil +} + +// Watch streans routing table events +func (r *Router) Watch(ctx context.Context, req *pb.WatchRequest, stream pb.Router_WatchStream) error { + watcher, err := r.Router.Watch() + if err != nil { + return errors.InternalServerError("go.micro.router", "failed creating event watcher: %v", err) + } + + defer stream.Close() + + for { + event, err := watcher.Next() + if err == router.ErrWatcherStopped { + break + } + + if err != nil { + return errors.InternalServerError("go.micro.router", "error watching events: %v", err) + } + + route := &pb.Route{ + Service: event.Route.Service, + Address: event.Route.Address, + Gateway: event.Route.Gateway, + Network: event.Route.Network, + Link: event.Route.Link, + Metric: int64(event.Route.Metric), + } + + tableEvent := &pb.Event{ + Type: pb.EventType(event.Type), + Timestamp: event.Timestamp.UnixNano(), + Route: route, + } + + if err := stream.Send(tableEvent); err != nil { + return err + } + } + + return nil +} diff --git a/router/handler/table.go b/router/handler/table.go new file mode 100644 index 00000000..ad17fa51 --- /dev/null +++ b/router/handler/table.go @@ -0,0 +1,114 @@ +package handler + +import ( + "context" + + "github.com/micro/go-micro/errors" + "github.com/micro/go-micro/router" + pb "github.com/micro/go-micro/router/proto" +) + +type Table struct { + Router router.Router +} + +func (t *Table) Create(ctx context.Context, route *pb.Route, resp *pb.CreateResponse) error { + err := t.Router.Table().Create(router.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int(route.Metric), + }) + if err != nil { + return errors.InternalServerError("go.micro.router", "failed to create route: %s", err) + } + + return nil +} + +func (t *Table) Update(ctx context.Context, route *pb.Route, resp *pb.UpdateResponse) error { + err := t.Router.Table().Update(router.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int(route.Metric), + }) + if err != nil { + return errors.InternalServerError("go.micro.router", "failed to update route: %s", err) + } + + return nil +} + +func (t *Table) Delete(ctx context.Context, route *pb.Route, resp *pb.DeleteResponse) error { + err := t.Router.Table().Delete(router.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int(route.Metric), + }) + if err != nil { + return errors.InternalServerError("go.micro.router", "failed to delete route: %s", err) + } + + return nil +} + +// List returns all routes in the routing table +func (t *Table) List(ctx context.Context, req *pb.Request, resp *pb.ListResponse) error { + routes, err := t.Router.Table().List() + if err != nil { + return errors.InternalServerError("go.micro.router", "failed to list routes: %s", err) + } + + var respRoutes []*pb.Route + for _, route := range routes { + respRoute := &pb.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int64(route.Metric), + } + respRoutes = append(respRoutes, respRoute) + } + + resp.Routes = respRoutes + + return nil +} + +func (t *Table) Query(ctx context.Context, req *pb.QueryRequest, resp *pb.QueryResponse) error { + query := router.NewQuery( + router.QueryService(req.Query.Service), + ) + + routes, err := t.Router.Table().Query(query) + if err != nil { + return errors.InternalServerError("go.micro.router", "failed to lookup routes: %s", err) + } + + var respRoutes []*pb.Route + for _, route := range routes { + respRoute := &pb.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int64(route.Metric), + } + respRoutes = append(respRoutes, respRoute) + } + + resp.Routes = respRoutes + + return nil +} diff --git a/router/options.go b/router/options.go new file mode 100644 index 00000000..13119e1b --- /dev/null +++ b/router/options.go @@ -0,0 +1,75 @@ +package router + +import ( + "github.com/google/uuid" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/registry" +) + +// Options are router options +type Options struct { + // Id is router id + Id string + // Address is router address + Address string + // Gateway is network gateway + Gateway string + // Network is network address + Network string + // Registry is the local registry + Registry registry.Registry + // Client for calling router + Client client.Client +} + +// Id sets Router Id +func Id(id string) Option { + return func(o *Options) { + o.Id = id + } +} + +// Address sets router service address +func Address(a string) Option { + return func(o *Options) { + o.Address = a + } +} + +// Client to call router service +func Client(c client.Client) Option { + return func(o *Options) { + o.Client = c + } +} + +// Gateway sets network gateway +func Gateway(g string) Option { + return func(o *Options) { + o.Gateway = g + } +} + +// Network sets router network +func Network(n string) Option { + return func(o *Options) { + o.Network = n + } +} + +// Registry sets the local registry +func Registry(r registry.Registry) Option { + return func(o *Options) { + o.Registry = r + } +} + +// DefaultOptions returns router default options +func DefaultOptions() Options { + return Options{ + Id: uuid.New().String(), + Address: DefaultAddress, + Network: DefaultNetwork, + Registry: registry.DefaultRegistry, + } +} diff --git a/router/proto/router.micro.go b/router/proto/router.micro.go new file mode 100644 index 00000000..b55c3043 --- /dev/null +++ b/router/proto/router.micro.go @@ -0,0 +1,416 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: micro/go-micro/router/proto/router.proto + +package go_micro_router + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +import ( + context "context" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/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 _ context.Context +var _ client.Option +var _ server.Option + +// Client API for Router service + +type RouterService interface { + Lookup(ctx context.Context, in *LookupRequest, opts ...client.CallOption) (*LookupResponse, error) + Watch(ctx context.Context, in *WatchRequest, opts ...client.CallOption) (Router_WatchService, error) + Advertise(ctx context.Context, in *Request, opts ...client.CallOption) (Router_AdvertiseService, error) + Process(ctx context.Context, in *Advert, opts ...client.CallOption) (*ProcessResponse, error) + Status(ctx context.Context, in *Request, opts ...client.CallOption) (*StatusResponse, error) +} + +type routerService struct { + c client.Client + name string +} + +func NewRouterService(name string, c client.Client) RouterService { + if c == nil { + c = client.NewClient() + } + if len(name) == 0 { + name = "go.micro.router" + } + return &routerService{ + c: c, + name: name, + } +} + +func (c *routerService) Lookup(ctx context.Context, in *LookupRequest, opts ...client.CallOption) (*LookupResponse, error) { + req := c.c.NewRequest(c.name, "Router.Lookup", in) + out := new(LookupResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerService) Watch(ctx context.Context, in *WatchRequest, opts ...client.CallOption) (Router_WatchService, error) { + req := c.c.NewRequest(c.name, "Router.Watch", &WatchRequest{}) + stream, err := c.c.Stream(ctx, req, opts...) + if err != nil { + return nil, err + } + if err := stream.Send(in); err != nil { + return nil, err + } + return &routerServiceWatch{stream}, nil +} + +type Router_WatchService interface { + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Recv() (*Event, error) +} + +type routerServiceWatch struct { + stream client.Stream +} + +func (x *routerServiceWatch) Close() error { + return x.stream.Close() +} + +func (x *routerServiceWatch) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *routerServiceWatch) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *routerServiceWatch) Recv() (*Event, error) { + m := new(Event) + err := x.stream.Recv(m) + if err != nil { + return nil, err + } + return m, nil +} + +func (c *routerService) Advertise(ctx context.Context, in *Request, opts ...client.CallOption) (Router_AdvertiseService, error) { + req := c.c.NewRequest(c.name, "Router.Advertise", &Request{}) + stream, err := c.c.Stream(ctx, req, opts...) + if err != nil { + return nil, err + } + if err := stream.Send(in); err != nil { + return nil, err + } + return &routerServiceAdvertise{stream}, nil +} + +type Router_AdvertiseService interface { + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Recv() (*Advert, error) +} + +type routerServiceAdvertise struct { + stream client.Stream +} + +func (x *routerServiceAdvertise) Close() error { + return x.stream.Close() +} + +func (x *routerServiceAdvertise) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *routerServiceAdvertise) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *routerServiceAdvertise) Recv() (*Advert, error) { + m := new(Advert) + err := x.stream.Recv(m) + if err != nil { + return nil, err + } + return m, nil +} + +func (c *routerService) Process(ctx context.Context, in *Advert, opts ...client.CallOption) (*ProcessResponse, error) { + req := c.c.NewRequest(c.name, "Router.Process", in) + out := new(ProcessResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerService) Status(ctx context.Context, in *Request, opts ...client.CallOption) (*StatusResponse, error) { + req := c.c.NewRequest(c.name, "Router.Status", in) + out := new(StatusResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Router service + +type RouterHandler interface { + Lookup(context.Context, *LookupRequest, *LookupResponse) error + Watch(context.Context, *WatchRequest, Router_WatchStream) error + Advertise(context.Context, *Request, Router_AdvertiseStream) error + Process(context.Context, *Advert, *ProcessResponse) error + Status(context.Context, *Request, *StatusResponse) error +} + +func RegisterRouterHandler(s server.Server, hdlr RouterHandler, opts ...server.HandlerOption) error { + type router interface { + Lookup(ctx context.Context, in *LookupRequest, out *LookupResponse) error + Watch(ctx context.Context, stream server.Stream) error + Advertise(ctx context.Context, stream server.Stream) error + Process(ctx context.Context, in *Advert, out *ProcessResponse) error + Status(ctx context.Context, in *Request, out *StatusResponse) error + } + type Router struct { + router + } + h := &routerHandler{hdlr} + return s.Handle(s.NewHandler(&Router{h}, opts...)) +} + +type routerHandler struct { + RouterHandler +} + +func (h *routerHandler) Lookup(ctx context.Context, in *LookupRequest, out *LookupResponse) error { + return h.RouterHandler.Lookup(ctx, in, out) +} + +func (h *routerHandler) Watch(ctx context.Context, stream server.Stream) error { + m := new(WatchRequest) + if err := stream.Recv(m); err != nil { + return err + } + return h.RouterHandler.Watch(ctx, m, &routerWatchStream{stream}) +} + +type Router_WatchStream interface { + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Send(*Event) error +} + +type routerWatchStream struct { + stream server.Stream +} + +func (x *routerWatchStream) Close() error { + return x.stream.Close() +} + +func (x *routerWatchStream) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *routerWatchStream) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *routerWatchStream) Send(m *Event) error { + return x.stream.Send(m) +} + +func (h *routerHandler) Advertise(ctx context.Context, stream server.Stream) error { + m := new(Request) + if err := stream.Recv(m); err != nil { + return err + } + return h.RouterHandler.Advertise(ctx, m, &routerAdvertiseStream{stream}) +} + +type Router_AdvertiseStream interface { + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Send(*Advert) error +} + +type routerAdvertiseStream struct { + stream server.Stream +} + +func (x *routerAdvertiseStream) Close() error { + return x.stream.Close() +} + +func (x *routerAdvertiseStream) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *routerAdvertiseStream) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *routerAdvertiseStream) Send(m *Advert) error { + return x.stream.Send(m) +} + +func (h *routerHandler) Process(ctx context.Context, in *Advert, out *ProcessResponse) error { + return h.RouterHandler.Process(ctx, in, out) +} + +func (h *routerHandler) Status(ctx context.Context, in *Request, out *StatusResponse) error { + return h.RouterHandler.Status(ctx, in, out) +} + +// Client API for Table service + +type TableService interface { + Create(ctx context.Context, in *Route, opts ...client.CallOption) (*CreateResponse, error) + Delete(ctx context.Context, in *Route, opts ...client.CallOption) (*DeleteResponse, error) + Update(ctx context.Context, in *Route, opts ...client.CallOption) (*UpdateResponse, error) + List(ctx context.Context, in *Request, opts ...client.CallOption) (*ListResponse, error) + Query(ctx context.Context, in *QueryRequest, opts ...client.CallOption) (*QueryResponse, error) +} + +type tableService struct { + c client.Client + name string +} + +func NewTableService(name string, c client.Client) TableService { + if c == nil { + c = client.NewClient() + } + if len(name) == 0 { + name = "go.micro.router" + } + return &tableService{ + c: c, + name: name, + } +} + +func (c *tableService) Create(ctx context.Context, in *Route, opts ...client.CallOption) (*CreateResponse, error) { + req := c.c.NewRequest(c.name, "Table.Create", in) + out := new(CreateResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tableService) Delete(ctx context.Context, in *Route, opts ...client.CallOption) (*DeleteResponse, error) { + req := c.c.NewRequest(c.name, "Table.Delete", in) + out := new(DeleteResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tableService) Update(ctx context.Context, in *Route, opts ...client.CallOption) (*UpdateResponse, error) { + req := c.c.NewRequest(c.name, "Table.Update", in) + out := new(UpdateResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tableService) List(ctx context.Context, in *Request, opts ...client.CallOption) (*ListResponse, error) { + req := c.c.NewRequest(c.name, "Table.List", in) + out := new(ListResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tableService) Query(ctx context.Context, in *QueryRequest, opts ...client.CallOption) (*QueryResponse, error) { + req := c.c.NewRequest(c.name, "Table.Query", in) + out := new(QueryResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Table service + +type TableHandler interface { + Create(context.Context, *Route, *CreateResponse) error + Delete(context.Context, *Route, *DeleteResponse) error + Update(context.Context, *Route, *UpdateResponse) error + List(context.Context, *Request, *ListResponse) error + Query(context.Context, *QueryRequest, *QueryResponse) error +} + +func RegisterTableHandler(s server.Server, hdlr TableHandler, opts ...server.HandlerOption) error { + type table interface { + Create(ctx context.Context, in *Route, out *CreateResponse) error + Delete(ctx context.Context, in *Route, out *DeleteResponse) error + Update(ctx context.Context, in *Route, out *UpdateResponse) error + List(ctx context.Context, in *Request, out *ListResponse) error + Query(ctx context.Context, in *QueryRequest, out *QueryResponse) error + } + type Table struct { + table + } + h := &tableHandler{hdlr} + return s.Handle(s.NewHandler(&Table{h}, opts...)) +} + +type tableHandler struct { + TableHandler +} + +func (h *tableHandler) Create(ctx context.Context, in *Route, out *CreateResponse) error { + return h.TableHandler.Create(ctx, in, out) +} + +func (h *tableHandler) Delete(ctx context.Context, in *Route, out *DeleteResponse) error { + return h.TableHandler.Delete(ctx, in, out) +} + +func (h *tableHandler) Update(ctx context.Context, in *Route, out *UpdateResponse) error { + return h.TableHandler.Update(ctx, in, out) +} + +func (h *tableHandler) List(ctx context.Context, in *Request, out *ListResponse) error { + return h.TableHandler.List(ctx, in, out) +} + +func (h *tableHandler) Query(ctx context.Context, in *QueryRequest, out *QueryResponse) error { + return h.TableHandler.Query(ctx, in, out) +} diff --git a/router/proto/router.pb.go b/router/proto/router.pb.go new file mode 100644 index 00000000..c448e484 --- /dev/null +++ b/router/proto/router.pb.go @@ -0,0 +1,1364 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: micro/go-micro/router/proto/router.proto + +package go_micro_router + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + 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 + +// AdvertType defines the type of advert +type AdvertType int32 + +const ( + AdvertType_AdvertAnnounce AdvertType = 0 + AdvertType_AdvertUpdate AdvertType = 1 +) + +var AdvertType_name = map[int32]string{ + 0: "AdvertAnnounce", + 1: "AdvertUpdate", +} + +var AdvertType_value = map[string]int32{ + "AdvertAnnounce": 0, + "AdvertUpdate": 1, +} + +func (x AdvertType) String() string { + return proto.EnumName(AdvertType_name, int32(x)) +} + +func (AdvertType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{0} +} + +// EventType defines the type of event +type EventType int32 + +const ( + EventType_Create EventType = 0 + EventType_Delete EventType = 1 + EventType_Update EventType = 2 +) + +var EventType_name = map[int32]string{ + 0: "Create", + 1: "Delete", + 2: "Update", +} + +var EventType_value = map[string]int32{ + "Create": 0, + "Delete": 1, + "Update": 2, +} + +func (x EventType) String() string { + return proto.EnumName(EventType_name, int32(x)) +} + +func (EventType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{1} +} + +// Empty request +type Request struct { + 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_6a36eee0b1adf739, []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 + +// ListResponse is returned by List +type ListResponse struct { + Routes []*Route `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListResponse) Reset() { *m = ListResponse{} } +func (m *ListResponse) String() string { return proto.CompactTextString(m) } +func (*ListResponse) ProtoMessage() {} +func (*ListResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{1} +} + +func (m *ListResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListResponse.Unmarshal(m, b) +} +func (m *ListResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListResponse.Marshal(b, m, deterministic) +} +func (m *ListResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListResponse.Merge(m, src) +} +func (m *ListResponse) XXX_Size() int { + return xxx_messageInfo_ListResponse.Size(m) +} +func (m *ListResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ListResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ListResponse proto.InternalMessageInfo + +func (m *ListResponse) GetRoutes() []*Route { + if m != nil { + return m.Routes + } + return nil +} + +// LookupRequest is made to Lookup +type LookupRequest struct { + Query *Query `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *LookupRequest) Reset() { *m = LookupRequest{} } +func (m *LookupRequest) String() string { return proto.CompactTextString(m) } +func (*LookupRequest) ProtoMessage() {} +func (*LookupRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{2} +} + +func (m *LookupRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_LookupRequest.Unmarshal(m, b) +} +func (m *LookupRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_LookupRequest.Marshal(b, m, deterministic) +} +func (m *LookupRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_LookupRequest.Merge(m, src) +} +func (m *LookupRequest) XXX_Size() int { + return xxx_messageInfo_LookupRequest.Size(m) +} +func (m *LookupRequest) XXX_DiscardUnknown() { + xxx_messageInfo_LookupRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_LookupRequest proto.InternalMessageInfo + +func (m *LookupRequest) GetQuery() *Query { + if m != nil { + return m.Query + } + return nil +} + +// LookupResponse is returned by Lookup +type LookupResponse struct { + Routes []*Route `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *LookupResponse) Reset() { *m = LookupResponse{} } +func (m *LookupResponse) String() string { return proto.CompactTextString(m) } +func (*LookupResponse) ProtoMessage() {} +func (*LookupResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{3} +} + +func (m *LookupResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_LookupResponse.Unmarshal(m, b) +} +func (m *LookupResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_LookupResponse.Marshal(b, m, deterministic) +} +func (m *LookupResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_LookupResponse.Merge(m, src) +} +func (m *LookupResponse) XXX_Size() int { + return xxx_messageInfo_LookupResponse.Size(m) +} +func (m *LookupResponse) XXX_DiscardUnknown() { + xxx_messageInfo_LookupResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_LookupResponse proto.InternalMessageInfo + +func (m *LookupResponse) GetRoutes() []*Route { + if m != nil { + return m.Routes + } + return nil +} + +type QueryRequest struct { + Query *Query `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *QueryRequest) Reset() { *m = QueryRequest{} } +func (m *QueryRequest) String() string { return proto.CompactTextString(m) } +func (*QueryRequest) ProtoMessage() {} +func (*QueryRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{4} +} + +func (m *QueryRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_QueryRequest.Unmarshal(m, b) +} +func (m *QueryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_QueryRequest.Marshal(b, m, deterministic) +} +func (m *QueryRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRequest.Merge(m, src) +} +func (m *QueryRequest) XXX_Size() int { + return xxx_messageInfo_QueryRequest.Size(m) +} +func (m *QueryRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRequest proto.InternalMessageInfo + +func (m *QueryRequest) GetQuery() *Query { + if m != nil { + return m.Query + } + return nil +} + +type QueryResponse struct { + Routes []*Route `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *QueryResponse) Reset() { *m = QueryResponse{} } +func (m *QueryResponse) String() string { return proto.CompactTextString(m) } +func (*QueryResponse) ProtoMessage() {} +func (*QueryResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{5} +} + +func (m *QueryResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_QueryResponse.Unmarshal(m, b) +} +func (m *QueryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_QueryResponse.Marshal(b, m, deterministic) +} +func (m *QueryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryResponse.Merge(m, src) +} +func (m *QueryResponse) XXX_Size() int { + return xxx_messageInfo_QueryResponse.Size(m) +} +func (m *QueryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryResponse proto.InternalMessageInfo + +func (m *QueryResponse) GetRoutes() []*Route { + if m != nil { + return m.Routes + } + return nil +} + +// WatchRequest is made to Watch Router +type WatchRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WatchRequest) Reset() { *m = WatchRequest{} } +func (m *WatchRequest) String() string { return proto.CompactTextString(m) } +func (*WatchRequest) ProtoMessage() {} +func (*WatchRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{6} +} + +func (m *WatchRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WatchRequest.Unmarshal(m, b) +} +func (m *WatchRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WatchRequest.Marshal(b, m, deterministic) +} +func (m *WatchRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WatchRequest.Merge(m, src) +} +func (m *WatchRequest) XXX_Size() int { + return xxx_messageInfo_WatchRequest.Size(m) +} +func (m *WatchRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WatchRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WatchRequest proto.InternalMessageInfo + +// Advert is router advertsement streamed by Watch +type Advert struct { + // id of the advertising router + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // type of advertisement + Type AdvertType `protobuf:"varint,2,opt,name=type,proto3,enum=go.micro.router.AdvertType" json:"type,omitempty"` + // unix timestamp of the advertisement + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // TTL of the Advert + Ttl int64 `protobuf:"varint,4,opt,name=ttl,proto3" json:"ttl,omitempty"` + // events is a list of advertised events + Events []*Event `protobuf:"bytes,5,rep,name=events,proto3" json:"events,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Advert) Reset() { *m = Advert{} } +func (m *Advert) String() string { return proto.CompactTextString(m) } +func (*Advert) ProtoMessage() {} +func (*Advert) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{7} +} + +func (m *Advert) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Advert.Unmarshal(m, b) +} +func (m *Advert) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Advert.Marshal(b, m, deterministic) +} +func (m *Advert) XXX_Merge(src proto.Message) { + xxx_messageInfo_Advert.Merge(m, src) +} +func (m *Advert) XXX_Size() int { + return xxx_messageInfo_Advert.Size(m) +} +func (m *Advert) XXX_DiscardUnknown() { + xxx_messageInfo_Advert.DiscardUnknown(m) +} + +var xxx_messageInfo_Advert proto.InternalMessageInfo + +func (m *Advert) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Advert) GetType() AdvertType { + if m != nil { + return m.Type + } + return AdvertType_AdvertAnnounce +} + +func (m *Advert) GetTimestamp() int64 { + if m != nil { + return m.Timestamp + } + return 0 +} + +func (m *Advert) GetTtl() int64 { + if m != nil { + return m.Ttl + } + return 0 +} + +func (m *Advert) GetEvents() []*Event { + if m != nil { + return m.Events + } + return nil +} + +// ProcessResponse is returned by Process +type ProcessResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ProcessResponse) Reset() { *m = ProcessResponse{} } +func (m *ProcessResponse) String() string { return proto.CompactTextString(m) } +func (*ProcessResponse) ProtoMessage() {} +func (*ProcessResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{8} +} + +func (m *ProcessResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ProcessResponse.Unmarshal(m, b) +} +func (m *ProcessResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ProcessResponse.Marshal(b, m, deterministic) +} +func (m *ProcessResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProcessResponse.Merge(m, src) +} +func (m *ProcessResponse) XXX_Size() int { + return xxx_messageInfo_ProcessResponse.Size(m) +} +func (m *ProcessResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ProcessResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ProcessResponse proto.InternalMessageInfo + +// CreateResponse is returned by Create +type CreateResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CreateResponse) Reset() { *m = CreateResponse{} } +func (m *CreateResponse) String() string { return proto.CompactTextString(m) } +func (*CreateResponse) ProtoMessage() {} +func (*CreateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{9} +} + +func (m *CreateResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CreateResponse.Unmarshal(m, b) +} +func (m *CreateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CreateResponse.Marshal(b, m, deterministic) +} +func (m *CreateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_CreateResponse.Merge(m, src) +} +func (m *CreateResponse) XXX_Size() int { + return xxx_messageInfo_CreateResponse.Size(m) +} +func (m *CreateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_CreateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_CreateResponse proto.InternalMessageInfo + +// DeleteResponse is returned by Delete +type DeleteResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DeleteResponse) Reset() { *m = DeleteResponse{} } +func (m *DeleteResponse) String() string { return proto.CompactTextString(m) } +func (*DeleteResponse) ProtoMessage() {} +func (*DeleteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{10} +} + +func (m *DeleteResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DeleteResponse.Unmarshal(m, b) +} +func (m *DeleteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DeleteResponse.Marshal(b, m, deterministic) +} +func (m *DeleteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeleteResponse.Merge(m, src) +} +func (m *DeleteResponse) XXX_Size() int { + return xxx_messageInfo_DeleteResponse.Size(m) +} +func (m *DeleteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_DeleteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_DeleteResponse proto.InternalMessageInfo + +// UpdateResponse is returned by Update +type UpdateResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateResponse) Reset() { *m = UpdateResponse{} } +func (m *UpdateResponse) String() string { return proto.CompactTextString(m) } +func (*UpdateResponse) ProtoMessage() {} +func (*UpdateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{11} +} + +func (m *UpdateResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateResponse.Unmarshal(m, b) +} +func (m *UpdateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateResponse.Marshal(b, m, deterministic) +} +func (m *UpdateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateResponse.Merge(m, src) +} +func (m *UpdateResponse) XXX_Size() int { + return xxx_messageInfo_UpdateResponse.Size(m) +} +func (m *UpdateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateResponse proto.InternalMessageInfo + +// Event is routing table event +type Event struct { + // type of event + Type EventType `protobuf:"varint,1,opt,name=type,proto3,enum=go.micro.router.EventType" json:"type,omitempty"` + // unix timestamp of event + Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // service route + Route *Route `protobuf:"bytes,3,opt,name=route,proto3" json:"route,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Event) Reset() { *m = Event{} } +func (m *Event) String() string { return proto.CompactTextString(m) } +func (*Event) ProtoMessage() {} +func (*Event) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{12} +} + +func (m *Event) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Event.Unmarshal(m, b) +} +func (m *Event) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Event.Marshal(b, m, deterministic) +} +func (m *Event) XXX_Merge(src proto.Message) { + xxx_messageInfo_Event.Merge(m, src) +} +func (m *Event) XXX_Size() int { + return xxx_messageInfo_Event.Size(m) +} +func (m *Event) XXX_DiscardUnknown() { + xxx_messageInfo_Event.DiscardUnknown(m) +} + +var xxx_messageInfo_Event proto.InternalMessageInfo + +func (m *Event) GetType() EventType { + if m != nil { + return m.Type + } + return EventType_Create +} + +func (m *Event) GetTimestamp() int64 { + if m != nil { + return m.Timestamp + } + return 0 +} + +func (m *Event) GetRoute() *Route { + if m != nil { + return m.Route + } + return nil +} + +// Query is passed in a LookupRequest +type Query struct { + // service to lookup + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + // gateway to lookup + Gateway string `protobuf:"bytes,2,opt,name=gateway,proto3" json:"gateway,omitempty"` + // network to lookup + Network string `protobuf:"bytes,3,opt,name=network,proto3" json:"network,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Query) Reset() { *m = Query{} } +func (m *Query) String() string { return proto.CompactTextString(m) } +func (*Query) ProtoMessage() {} +func (*Query) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{13} +} + +func (m *Query) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Query.Unmarshal(m, b) +} +func (m *Query) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Query.Marshal(b, m, deterministic) +} +func (m *Query) XXX_Merge(src proto.Message) { + xxx_messageInfo_Query.Merge(m, src) +} +func (m *Query) XXX_Size() int { + return xxx_messageInfo_Query.Size(m) +} +func (m *Query) XXX_DiscardUnknown() { + xxx_messageInfo_Query.DiscardUnknown(m) +} + +var xxx_messageInfo_Query proto.InternalMessageInfo + +func (m *Query) GetService() string { + if m != nil { + return m.Service + } + return "" +} + +func (m *Query) GetGateway() string { + if m != nil { + return m.Gateway + } + return "" +} + +func (m *Query) GetNetwork() string { + if m != nil { + return m.Network + } + return "" +} + +// Route is a service route +type Route struct { + // service for the route + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + // the address that advertise this route + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + // gateway as the next hop + Gateway string `protobuf:"bytes,3,opt,name=gateway,proto3" json:"gateway,omitempty"` + // the network for this destination + Network string `protobuf:"bytes,4,opt,name=network,proto3" json:"network,omitempty"` + // the network link + Link string `protobuf:"bytes,5,opt,name=link,proto3" json:"link,omitempty"` + // the metric / score of this route + Metric int64 `protobuf:"varint,6,opt,name=metric,proto3" json:"metric,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Route) Reset() { *m = Route{} } +func (m *Route) String() string { return proto.CompactTextString(m) } +func (*Route) ProtoMessage() {} +func (*Route) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{14} +} + +func (m *Route) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Route.Unmarshal(m, b) +} +func (m *Route) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Route.Marshal(b, m, deterministic) +} +func (m *Route) XXX_Merge(src proto.Message) { + xxx_messageInfo_Route.Merge(m, src) +} +func (m *Route) XXX_Size() int { + return xxx_messageInfo_Route.Size(m) +} +func (m *Route) XXX_DiscardUnknown() { + xxx_messageInfo_Route.DiscardUnknown(m) +} + +var xxx_messageInfo_Route proto.InternalMessageInfo + +func (m *Route) GetService() string { + if m != nil { + return m.Service + } + return "" +} + +func (m *Route) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *Route) GetGateway() string { + if m != nil { + return m.Gateway + } + return "" +} + +func (m *Route) GetNetwork() string { + if m != nil { + return m.Network + } + return "" +} + +func (m *Route) GetLink() string { + if m != nil { + return m.Link + } + return "" +} + +func (m *Route) GetMetric() int64 { + if m != nil { + return m.Metric + } + return 0 +} + +type Status struct { + Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{15} +} + +func (m *Status) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Status.Unmarshal(m, b) +} +func (m *Status) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Status.Marshal(b, m, deterministic) +} +func (m *Status) XXX_Merge(src proto.Message) { + xxx_messageInfo_Status.Merge(m, src) +} +func (m *Status) XXX_Size() int { + return xxx_messageInfo_Status.Size(m) +} +func (m *Status) XXX_DiscardUnknown() { + xxx_messageInfo_Status.DiscardUnknown(m) +} + +var xxx_messageInfo_Status proto.InternalMessageInfo + +func (m *Status) GetCode() string { + if m != nil { + return m.Code + } + return "" +} + +func (m *Status) GetError() string { + if m != nil { + return m.Error + } + return "" +} + +type StatusResponse struct { + Status *Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *StatusResponse) Reset() { *m = StatusResponse{} } +func (m *StatusResponse) String() string { return proto.CompactTextString(m) } +func (*StatusResponse) ProtoMessage() {} +func (*StatusResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a36eee0b1adf739, []int{16} +} + +func (m *StatusResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_StatusResponse.Unmarshal(m, b) +} +func (m *StatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_StatusResponse.Marshal(b, m, deterministic) +} +func (m *StatusResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_StatusResponse.Merge(m, src) +} +func (m *StatusResponse) XXX_Size() int { + return xxx_messageInfo_StatusResponse.Size(m) +} +func (m *StatusResponse) XXX_DiscardUnknown() { + xxx_messageInfo_StatusResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_StatusResponse proto.InternalMessageInfo + +func (m *StatusResponse) GetStatus() *Status { + if m != nil { + return m.Status + } + return nil +} + +func init() { + proto.RegisterEnum("go.micro.router.AdvertType", AdvertType_name, AdvertType_value) + proto.RegisterEnum("go.micro.router.EventType", EventType_name, EventType_value) + proto.RegisterType((*Request)(nil), "go.micro.router.Request") + proto.RegisterType((*ListResponse)(nil), "go.micro.router.ListResponse") + proto.RegisterType((*LookupRequest)(nil), "go.micro.router.LookupRequest") + proto.RegisterType((*LookupResponse)(nil), "go.micro.router.LookupResponse") + proto.RegisterType((*QueryRequest)(nil), "go.micro.router.QueryRequest") + proto.RegisterType((*QueryResponse)(nil), "go.micro.router.QueryResponse") + proto.RegisterType((*WatchRequest)(nil), "go.micro.router.WatchRequest") + proto.RegisterType((*Advert)(nil), "go.micro.router.Advert") + proto.RegisterType((*ProcessResponse)(nil), "go.micro.router.ProcessResponse") + proto.RegisterType((*CreateResponse)(nil), "go.micro.router.CreateResponse") + proto.RegisterType((*DeleteResponse)(nil), "go.micro.router.DeleteResponse") + proto.RegisterType((*UpdateResponse)(nil), "go.micro.router.UpdateResponse") + proto.RegisterType((*Event)(nil), "go.micro.router.Event") + proto.RegisterType((*Query)(nil), "go.micro.router.Query") + proto.RegisterType((*Route)(nil), "go.micro.router.Route") + proto.RegisterType((*Status)(nil), "go.micro.router.Status") + proto.RegisterType((*StatusResponse)(nil), "go.micro.router.StatusResponse") +} + +func init() { + proto.RegisterFile("micro/go-micro/router/proto/router.proto", fileDescriptor_6a36eee0b1adf739) +} + +var fileDescriptor_6a36eee0b1adf739 = []byte{ + // 689 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x4e, 0xdb, 0x40, + 0x10, 0xb6, 0x93, 0xd8, 0x28, 0xd3, 0x10, 0xdc, 0x51, 0x05, 0x56, 0x5a, 0x20, 0xf2, 0x29, 0x42, + 0xd4, 0xa9, 0xd2, 0x6b, 0xff, 0x02, 0xa5, 0xaa, 0x54, 0x0e, 0xad, 0x0b, 0xea, 0xd9, 0xd8, 0x23, + 0x6a, 0x91, 0xd8, 0x66, 0x77, 0x03, 0xca, 0xb9, 0x8f, 0xd1, 0x27, 0xe8, 0x73, 0xf5, 0xda, 0x87, + 0xa8, 0xbc, 0xbb, 0x0e, 0x21, 0xc6, 0x48, 0x70, 0xf2, 0xce, 0xdf, 0x37, 0xff, 0x63, 0x18, 0x4c, + 0x93, 0x88, 0x65, 0xc3, 0xf3, 0xec, 0xa5, 0x7a, 0xb0, 0x6c, 0x26, 0x88, 0x0d, 0x73, 0x96, 0x89, + 0x92, 0xf0, 0x25, 0x81, 0x1b, 0xe7, 0x99, 0x2f, 0x75, 0x7c, 0xc5, 0xf6, 0xda, 0xb0, 0x16, 0xd0, + 0xe5, 0x8c, 0xb8, 0xf0, 0xde, 0x41, 0xe7, 0x38, 0xe1, 0x22, 0x20, 0x9e, 0x67, 0x29, 0x27, 0xf4, + 0xc1, 0x96, 0x4a, 0xdc, 0x35, 0xfb, 0xcd, 0xc1, 0x93, 0xd1, 0xa6, 0xbf, 0x62, 0xec, 0x07, 0xc5, + 0x27, 0xd0, 0x5a, 0xde, 0x5b, 0x58, 0x3f, 0xce, 0xb2, 0x8b, 0x59, 0xae, 0x01, 0x71, 0x1f, 0xac, + 0xcb, 0x19, 0xb1, 0xb9, 0x6b, 0xf6, 0xcd, 0x3b, 0xed, 0xbf, 0x15, 0xd2, 0x40, 0x29, 0x79, 0x1f, + 0xa0, 0x5b, 0x9a, 0x3f, 0x32, 0x80, 0x37, 0xd0, 0x51, 0x88, 0x8f, 0xf2, 0xff, 0x1e, 0xd6, 0xb5, + 0xf5, 0x23, 0xdd, 0x77, 0xa1, 0xf3, 0x23, 0x14, 0xd1, 0xcf, 0xb2, 0x9e, 0x7f, 0x4c, 0xb0, 0xc7, + 0xf1, 0x15, 0x31, 0x81, 0x5d, 0x68, 0x24, 0xb1, 0x0c, 0xa3, 0x1d, 0x34, 0x92, 0x18, 0x87, 0xd0, + 0x12, 0xf3, 0x9c, 0xdc, 0x46, 0xdf, 0x1c, 0x74, 0x47, 0xcf, 0x2b, 0xc0, 0xca, 0xec, 0x64, 0x9e, + 0x53, 0x20, 0x15, 0xf1, 0x05, 0xb4, 0x45, 0x32, 0x25, 0x2e, 0xc2, 0x69, 0xee, 0x36, 0xfb, 0xe6, + 0xa0, 0x19, 0xdc, 0x30, 0xd0, 0x81, 0xa6, 0x10, 0x13, 0xb7, 0x25, 0xf9, 0xc5, 0xb3, 0x88, 0x9d, + 0xae, 0x28, 0x15, 0xdc, 0xb5, 0x6a, 0x62, 0x3f, 0x2a, 0xc4, 0x81, 0xd6, 0xf2, 0x9e, 0xc2, 0xc6, + 0x57, 0x96, 0x45, 0xc4, 0x79, 0x99, 0xbe, 0xe7, 0x40, 0xf7, 0x90, 0x51, 0x28, 0x68, 0x99, 0xf3, + 0x91, 0x26, 0x74, 0x9b, 0x73, 0x9a, 0xc7, 0xcb, 0x3a, 0xbf, 0x4c, 0xb0, 0x24, 0x34, 0xfa, 0x3a, + 0x47, 0x53, 0xe6, 0xd8, 0xbb, 0x3b, 0x80, 0xba, 0x14, 0x1b, 0xab, 0x29, 0xee, 0x83, 0x25, 0xed, + 0x64, 0xf2, 0xf5, 0xbd, 0x50, 0x4a, 0xde, 0x29, 0x58, 0xb2, 0x97, 0xe8, 0xc2, 0x1a, 0x27, 0x76, + 0x95, 0x44, 0xa4, 0xab, 0x5f, 0x92, 0x85, 0xe4, 0x3c, 0x14, 0x74, 0x1d, 0xce, 0xa5, 0xb3, 0x76, + 0x50, 0x92, 0x85, 0x24, 0x25, 0x71, 0x9d, 0xb1, 0x0b, 0xe9, 0xac, 0x1d, 0x94, 0xa4, 0xf7, 0xdb, + 0x04, 0x4b, 0xfa, 0xb9, 0x1f, 0x37, 0x8c, 0x63, 0x46, 0x9c, 0x97, 0xb8, 0x9a, 0x5c, 0xf6, 0xd8, + 0xac, 0xf5, 0xd8, 0xba, 0xe5, 0x11, 0x11, 0x5a, 0x93, 0x24, 0xbd, 0x70, 0x2d, 0xc9, 0x96, 0x6f, + 0xdc, 0x04, 0x7b, 0x4a, 0x82, 0x25, 0x91, 0x6b, 0xcb, 0x2a, 0x69, 0xca, 0x1b, 0x81, 0xfd, 0x5d, + 0x84, 0x62, 0xc6, 0x0b, 0xab, 0x28, 0x8b, 0xcb, 0xd0, 0xe4, 0x1b, 0x9f, 0x81, 0x45, 0x8c, 0x65, + 0x4c, 0x47, 0xa5, 0x08, 0x6f, 0x0c, 0x5d, 0x65, 0xb3, 0x98, 0xfa, 0x21, 0xd8, 0x5c, 0x72, 0xf4, + 0xd6, 0x6c, 0x55, 0x2a, 0xad, 0x0d, 0xb4, 0xda, 0xde, 0x08, 0xe0, 0x66, 0x5c, 0x11, 0xa1, 0xab, + 0xa8, 0x71, 0x9a, 0x66, 0xb3, 0x34, 0x22, 0xc7, 0x40, 0x07, 0x3a, 0x8a, 0xa7, 0x66, 0xc5, 0x31, + 0xf7, 0x86, 0xd0, 0x5e, 0xb4, 0x1f, 0x01, 0x6c, 0x35, 0x68, 0x8e, 0x51, 0xbc, 0xd5, 0x88, 0x39, + 0x66, 0xf1, 0xd6, 0x06, 0x8d, 0xd1, 0xbf, 0x06, 0xd8, 0xb2, 0xf2, 0x0c, 0xbf, 0x80, 0xad, 0xee, + 0x04, 0xee, 0x54, 0x42, 0xbb, 0x75, 0x7f, 0x7a, 0xbb, 0xb5, 0x72, 0x3d, 0xac, 0x06, 0x1e, 0x80, + 0x25, 0x77, 0x16, 0xb7, 0x2b, 0xba, 0xcb, 0xbb, 0xdc, 0xab, 0xd9, 0x1f, 0xcf, 0x78, 0x65, 0xe2, + 0x01, 0xb4, 0x55, 0x7a, 0x09, 0x27, 0x74, 0xab, 0x83, 0xa9, 0x21, 0xb6, 0x6a, 0xb6, 0x5c, 0x62, + 0x7c, 0x82, 0x35, 0xbd, 0x7f, 0x58, 0xa7, 0xd7, 0xeb, 0x57, 0x04, 0xab, 0x2b, 0x6b, 0xe0, 0xd1, + 0x62, 0x06, 0xea, 0x03, 0xd9, 0xad, 0xeb, 0xe8, 0x02, 0x66, 0xf4, 0xb7, 0x01, 0xd6, 0x49, 0x78, + 0x36, 0x21, 0x3c, 0x2c, 0x9b, 0x83, 0x35, 0x2b, 0x77, 0x07, 0xdc, 0xca, 0xd9, 0x30, 0x0a, 0x10, + 0xd5, 0xd5, 0x07, 0x80, 0xac, 0x5c, 0x1a, 0x09, 0xa2, 0xc6, 0xe1, 0x01, 0x20, 0x2b, 0xc7, 0xc9, + 0xc0, 0x31, 0xb4, 0x8a, 0x7f, 0xdc, 0x3d, 0xd5, 0xa9, 0x0e, 0xc2, 0xf2, 0x4f, 0xd1, 0x33, 0xf0, + 0x73, 0x79, 0x5b, 0xb6, 0x6b, 0xfe, 0x27, 0x1a, 0x68, 0xa7, 0x4e, 0x5c, 0x22, 0x9d, 0xd9, 0xf2, + 0x9f, 0xfc, 0xfa, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xd0, 0xc0, 0x27, 0xbf, 0x07, 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 + +// RouterClient is the client API for Router service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type RouterClient interface { + Lookup(ctx context.Context, in *LookupRequest, opts ...grpc.CallOption) (*LookupResponse, error) + Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (Router_WatchClient, error) + Advertise(ctx context.Context, in *Request, opts ...grpc.CallOption) (Router_AdvertiseClient, error) + Process(ctx context.Context, in *Advert, opts ...grpc.CallOption) (*ProcessResponse, error) + Status(ctx context.Context, in *Request, opts ...grpc.CallOption) (*StatusResponse, error) +} + +type routerClient struct { + cc *grpc.ClientConn +} + +func NewRouterClient(cc *grpc.ClientConn) RouterClient { + return &routerClient{cc} +} + +func (c *routerClient) Lookup(ctx context.Context, in *LookupRequest, opts ...grpc.CallOption) (*LookupResponse, error) { + out := new(LookupResponse) + err := c.cc.Invoke(ctx, "/go.micro.router.Router/Lookup", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerClient) Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (Router_WatchClient, error) { + stream, err := c.cc.NewStream(ctx, &_Router_serviceDesc.Streams[0], "/go.micro.router.Router/Watch", opts...) + if err != nil { + return nil, err + } + x := &routerWatchClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Router_WatchClient interface { + Recv() (*Event, error) + grpc.ClientStream +} + +type routerWatchClient struct { + grpc.ClientStream +} + +func (x *routerWatchClient) Recv() (*Event, error) { + m := new(Event) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routerClient) Advertise(ctx context.Context, in *Request, opts ...grpc.CallOption) (Router_AdvertiseClient, error) { + stream, err := c.cc.NewStream(ctx, &_Router_serviceDesc.Streams[1], "/go.micro.router.Router/Advertise", opts...) + if err != nil { + return nil, err + } + x := &routerAdvertiseClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Router_AdvertiseClient interface { + Recv() (*Advert, error) + grpc.ClientStream +} + +type routerAdvertiseClient struct { + grpc.ClientStream +} + +func (x *routerAdvertiseClient) Recv() (*Advert, error) { + m := new(Advert) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routerClient) Process(ctx context.Context, in *Advert, opts ...grpc.CallOption) (*ProcessResponse, error) { + out := new(ProcessResponse) + err := c.cc.Invoke(ctx, "/go.micro.router.Router/Process", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerClient) Status(ctx context.Context, in *Request, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := c.cc.Invoke(ctx, "/go.micro.router.Router/Status", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RouterServer is the server API for Router service. +type RouterServer interface { + Lookup(context.Context, *LookupRequest) (*LookupResponse, error) + Watch(*WatchRequest, Router_WatchServer) error + Advertise(*Request, Router_AdvertiseServer) error + Process(context.Context, *Advert) (*ProcessResponse, error) + Status(context.Context, *Request) (*StatusResponse, error) +} + +func RegisterRouterServer(s *grpc.Server, srv RouterServer) { + s.RegisterService(&_Router_serviceDesc, srv) +} + +func _Router_Lookup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LookupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).Lookup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.router.Router/Lookup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).Lookup(ctx, req.(*LookupRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Router_Watch_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RouterServer).Watch(m, &routerWatchServer{stream}) +} + +type Router_WatchServer interface { + Send(*Event) error + grpc.ServerStream +} + +type routerWatchServer struct { + grpc.ServerStream +} + +func (x *routerWatchServer) Send(m *Event) error { + return x.ServerStream.SendMsg(m) +} + +func _Router_Advertise_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(Request) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RouterServer).Advertise(m, &routerAdvertiseServer{stream}) +} + +type Router_AdvertiseServer interface { + Send(*Advert) error + grpc.ServerStream +} + +type routerAdvertiseServer struct { + grpc.ServerStream +} + +func (x *routerAdvertiseServer) Send(m *Advert) error { + return x.ServerStream.SendMsg(m) +} + +func _Router_Process_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Advert) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).Process(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.router.Router/Process", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).Process(ctx, req.(*Advert)) + } + return interceptor(ctx, in, info, handler) +} + +func _Router_Status_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.(RouterServer).Status(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.router.Router/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).Status(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +var _Router_serviceDesc = grpc.ServiceDesc{ + ServiceName: "go.micro.router.Router", + HandlerType: (*RouterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Lookup", + Handler: _Router_Lookup_Handler, + }, + { + MethodName: "Process", + Handler: _Router_Process_Handler, + }, + { + MethodName: "Status", + Handler: _Router_Status_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Watch", + Handler: _Router_Watch_Handler, + ServerStreams: true, + }, + { + StreamName: "Advertise", + Handler: _Router_Advertise_Handler, + ServerStreams: true, + }, + }, + Metadata: "micro/go-micro/router/proto/router.proto", +} + +// TableClient is the client API for Table service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type TableClient interface { + Create(ctx context.Context, in *Route, opts ...grpc.CallOption) (*CreateResponse, error) + Delete(ctx context.Context, in *Route, opts ...grpc.CallOption) (*DeleteResponse, error) + Update(ctx context.Context, in *Route, opts ...grpc.CallOption) (*UpdateResponse, error) + List(ctx context.Context, in *Request, opts ...grpc.CallOption) (*ListResponse, error) + Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) +} + +type tableClient struct { + cc *grpc.ClientConn +} + +func NewTableClient(cc *grpc.ClientConn) TableClient { + return &tableClient{cc} +} + +func (c *tableClient) Create(ctx context.Context, in *Route, opts ...grpc.CallOption) (*CreateResponse, error) { + out := new(CreateResponse) + err := c.cc.Invoke(ctx, "/go.micro.router.Table/Create", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tableClient) Delete(ctx context.Context, in *Route, opts ...grpc.CallOption) (*DeleteResponse, error) { + out := new(DeleteResponse) + err := c.cc.Invoke(ctx, "/go.micro.router.Table/Delete", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tableClient) Update(ctx context.Context, in *Route, opts ...grpc.CallOption) (*UpdateResponse, error) { + out := new(UpdateResponse) + err := c.cc.Invoke(ctx, "/go.micro.router.Table/Update", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tableClient) List(ctx context.Context, in *Request, opts ...grpc.CallOption) (*ListResponse, error) { + out := new(ListResponse) + err := c.cc.Invoke(ctx, "/go.micro.router.Table/List", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tableClient) Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) { + out := new(QueryResponse) + err := c.cc.Invoke(ctx, "/go.micro.router.Table/Query", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TableServer is the server API for Table service. +type TableServer interface { + Create(context.Context, *Route) (*CreateResponse, error) + Delete(context.Context, *Route) (*DeleteResponse, error) + Update(context.Context, *Route) (*UpdateResponse, error) + List(context.Context, *Request) (*ListResponse, error) + Query(context.Context, *QueryRequest) (*QueryResponse, error) +} + +func RegisterTableServer(s *grpc.Server, srv TableServer) { + s.RegisterService(&_Table_serviceDesc, srv) +} + +func _Table_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Route) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TableServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.router.Table/Create", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TableServer).Create(ctx, req.(*Route)) + } + return interceptor(ctx, in, info, handler) +} + +func _Table_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Route) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TableServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.router.Table/Delete", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TableServer).Delete(ctx, req.(*Route)) + } + return interceptor(ctx, in, info, handler) +} + +func _Table_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Route) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TableServer).Update(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.router.Table/Update", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TableServer).Update(ctx, req.(*Route)) + } + return interceptor(ctx, in, info, handler) +} + +func _Table_List_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.(TableServer).List(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.router.Table/List", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TableServer).List(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _Table_Query_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TableServer).Query(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.router.Table/Query", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TableServer).Query(ctx, req.(*QueryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Table_serviceDesc = grpc.ServiceDesc{ + ServiceName: "go.micro.router.Table", + HandlerType: (*TableServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Create", + Handler: _Table_Create_Handler, + }, + { + MethodName: "Delete", + Handler: _Table_Delete_Handler, + }, + { + MethodName: "Update", + Handler: _Table_Update_Handler, + }, + { + MethodName: "List", + Handler: _Table_List_Handler, + }, + { + MethodName: "Query", + Handler: _Table_Query_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "micro/go-micro/router/proto/router.proto", +} diff --git a/router/proto/router.proto b/router/proto/router.proto new file mode 100644 index 00000000..3e18d456 --- /dev/null +++ b/router/proto/router.proto @@ -0,0 +1,133 @@ +syntax = "proto3"; + +package go.micro.router; + +// Router service is used by the proxy to lookup routes +service Router { + rpc Lookup(LookupRequest) returns (LookupResponse) {}; + rpc Watch(WatchRequest) returns (stream Event) {}; + rpc Advertise(Request) returns (stream Advert) {}; + rpc Process(Advert) returns (ProcessResponse) {}; + rpc Status(Request) returns (StatusResponse) {}; +} + +service Table { + rpc Create(Route) returns (CreateResponse) {}; + rpc Delete(Route) returns (DeleteResponse) {}; + rpc Update(Route) returns (UpdateResponse) {}; + rpc List(Request) returns (ListResponse) {}; + rpc Query(QueryRequest) returns (QueryResponse) {}; +} + +// Empty request +message Request {} + +// ListResponse is returned by List +message ListResponse { + repeated Route routes = 1; +} + +// LookupRequest is made to Lookup +message LookupRequest { + Query query = 1; +} + +// LookupResponse is returned by Lookup +message LookupResponse { + repeated Route routes = 1; +} + +message QueryRequest{ + Query query = 1; +} + +message QueryResponse { + repeated Route routes = 1; +} + +// WatchRequest is made to Watch Router +message WatchRequest {} + +// AdvertType defines the type of advert +enum AdvertType { + AdvertAnnounce = 0; + AdvertUpdate = 1; +} + +// Advert is router advertsement streamed by Watch +message Advert { + // id of the advertising router + string id = 1; + // type of advertisement + AdvertType type = 2; + // unix timestamp of the advertisement + int64 timestamp = 3; + // TTL of the Advert + int64 ttl = 4; + // events is a list of advertised events + repeated Event events = 5; +} + +// ProcessResponse is returned by Process +message ProcessResponse {} + +// CreateResponse is returned by Create +message CreateResponse {} + +// DeleteResponse is returned by Delete +message DeleteResponse {} + +// UpdateResponse is returned by Update +message UpdateResponse {} + +// EventType defines the type of event +enum EventType { + Create = 0; + Delete = 1; + Update = 2; +} + +// Event is routing table event +message Event { + // type of event + EventType type = 1; + // unix timestamp of event + int64 timestamp = 2; + // service route + Route route = 3; +} + +// Query is passed in a LookupRequest +message Query { + // service to lookup + string service = 1; + // gateway to lookup + string gateway = 2; + // network to lookup + string network = 3; +} + +// Route is a service route +message Route { + // service for the route + string service = 1; + // the address that advertise this route + string address = 2; + // gateway as the next hop + string gateway = 3; + // the network for this destination + string network = 4; + // the network link + string link = 5; + // the metric / score of this route + int64 metric = 6; +} + +message Status { + string code = 1; + string error = 2; +} + +message StatusResponse { + Status status = 1; +} diff --git a/router/query.go b/router/query.go new file mode 100644 index 00000000..68fb3588 --- /dev/null +++ b/router/query.go @@ -0,0 +1,74 @@ +package router + +// QueryOption sets routing table query options +type QueryOption func(*QueryOptions) + +// QueryOptions are routing table query options +type QueryOptions struct { + // Service is destination service name + Service string + // Gateway is route gateway + Gateway string + // Network is network address + Network string +} + +// QueryService sets destination address +func QueryService(s string) QueryOption { + return func(o *QueryOptions) { + o.Service = s + } +} + +// QueryGateway sets route gateway +func QueryGateway(g string) QueryOption { + return func(o *QueryOptions) { + o.Gateway = g + } +} + +// QueryNetwork sets route network address +func QueryNetwork(n string) QueryOption { + return func(o *QueryOptions) { + o.Network = n + } +} + +// Query is routing table query +type Query interface { + // Options returns query options + Options() QueryOptions +} + +// query is a basic implementation of Query +type query struct { + opts QueryOptions +} + +// NewQuery creates new query and returns it +func NewQuery(opts ...QueryOption) Query { + // default options + qopts := QueryOptions{ + Service: "*", + Gateway: "*", + Network: "*", + } + + for _, o := range opts { + o(&qopts) + } + + return &query{ + opts: qopts, + } +} + +// Options returns query options +func (q *query) Options() QueryOptions { + return q.opts +} + +// String prints routing table query in human readable form +func (q query) String() string { + return "query" +} diff --git a/router/route.go b/router/route.go new file mode 100644 index 00000000..fa9dbc72 --- /dev/null +++ b/router/route.go @@ -0,0 +1,38 @@ +package router + +import ( + "hash/fnv" +) + +var ( + // DefaultLink is default network link + DefaultLink = "local" + // DefaultLocalMetric is default route cost metric for the local network + DefaultLocalMetric = 1 + // DefaultNetworkMetric is default route cost metric for the micro network + DefaultNetworkMetric = 10 +) + +// 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 + // Link is network link + Link string + // Metric is the route cost metric + Metric int +} + +// 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.Link)) + return h.Sum64() +} diff --git a/router/route_test.go b/router/route_test.go new file mode 100644 index 00000000..483b852c --- /dev/null +++ b/router/route_test.go @@ -0,0 +1,24 @@ +package router + +import "testing" + +func TestHash(t *testing.T) { + route1 := Route{ + Service: "dest.svc", + Gateway: "dest.gw", + Network: "dest.network", + Link: "det.link", + Metric: 10, + } + + // make a copy + route2 := route1 + + route1Hash := route1.Hash() + route2Hash := route2.Hash() + + // we should get the same hash + if route1Hash != route2Hash { + t.Errorf("identical routes result in different hashes") + } +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 00000000..af6f8bc3 --- /dev/null +++ b/router/router.go @@ -0,0 +1,135 @@ +// Package router provides a network routing control plane +package router + +import ( + "time" +) + +// Router is an interface for a routing control plane +type Router interface { + // Init initializes the router with options + Init(...Option) error + // Options returns the router options + Options() Options + // The routing table + Table() Table + // Advertise advertises routes to the network + Advertise() (<-chan *Advert, error) + // Process processes incoming adverts + Process(*Advert) error + // Lookup queries routes in the routing table + Lookup(Query) ([]Route, error) + // Watch returns a watcher which tracks updates to the routing table + Watch(opts ...WatchOption) (Watcher, error) + // Status returns router status + Status() Status + // Stop stops the router + Stop() error + // Returns the router implementation + String() string +} + +type Table interface { + // Create new route in the routing table + Create(Route) error + // Delete deletes existing route from the routing table + Delete(Route) error + // Update updates route in the routing table + Update(Route) error + // List returns the list of all routes in the table + List() ([]Route, error) + // Query queries routes in the routing table + Query(Query) ([]Route, error) +} + +// Option used by the router +type Option func(*Options) + +// StatusCode defines router status +type StatusCode int + +const ( + // Running means the router is up and running + Running StatusCode = iota + // Advertising means the router is advertising + Advertising + // Stopped means the router has been stopped + Stopped + // Error means the router has encountered error + Error +) + +func (s StatusCode) String() string { + switch s { + case Running: + return "running" + case Advertising: + return "advertising" + case Stopped: + return "stopped" + case Error: + return "error" + default: + return "unknown" + } +} + +// Status is router status +type Status struct { + // Error is router error + Error error + // Code defines router status + Code StatusCode +} + +// AdvertType is route advertisement type +type AdvertType int + +const ( + // Announce is advertised when the router announces itself + Announce AdvertType = iota + // RouteUpdate advertises route updates + RouteUpdate +) + +// String returns human readable advertisement type +func (t AdvertType) String() string { + switch t { + case Announce: + return "announce" + case RouteUpdate: + return "update" + default: + return "unknown" + } +} + +// Advert contains a list of events advertised by the router to the network +type Advert struct { + // Id is the router Id + Id string + // Type is type of advert + Type AdvertType + // Timestamp marks the time when the update is sent + Timestamp time.Time + // TTL is Advert TTL + TTL time.Duration + // Events is a list of routing table events to advertise + Events []*Event +} + +var ( + // DefaultAddress is default router address + DefaultAddress = ":9093" + // DefaultName is default router service name + DefaultName = "go.micro.router" + // DefaultNetwork is default micro network + DefaultNetwork = "go.micro" + // DefaultRouter is default network router + DefaultRouter = NewRouter() +) + +// NewRouter creates new Router and returns it +func NewRouter(opts ...Option) Router { + return newRouter(opts...) +} diff --git a/router/service/service.go b/router/service/service.go new file mode 100644 index 00000000..b06de9c8 --- /dev/null +++ b/router/service/service.go @@ -0,0 +1,316 @@ +package service + +import ( + "context" + "errors" + "fmt" + "io" + "sync" + "time" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/router" + pb "github.com/micro/go-micro/router/proto" +) + +type svc struct { + sync.RWMutex + opts router.Options + callOpts []client.CallOption + router pb.RouterService + table *table + status *router.Status + exit chan bool + errChan chan error + advertChan chan *router.Advert +} + +// NewRouter creates new service router and returns it +func NewRouter(opts ...router.Option) router.Router { + // get default options + options := router.DefaultOptions() + + // apply requested options + for _, o := range opts { + o(&options) + } + + // NOTE: might need some client opts here + cli := client.DefaultClient + + // set options client + if options.Client != nil { + cli = options.Client + } + + // NOTE: should we have Client/Service option in router.Options? + s := &svc{ + opts: options, + router: pb.NewRouterService(router.DefaultName, cli), + } + + // set the router address to call + if len(options.Address) > 0 { + s.callOpts = []client.CallOption{ + client.WithAddress(options.Address), + } + } + // set the table + s.table = &table{pb.NewTableService(router.DefaultName, cli), s.callOpts} + + return s +} + +// Init initializes router with given options +func (s *svc) Init(opts ...router.Option) error { + for _, o := range opts { + o(&s.opts) + } + return nil +} + +// Options returns router options +func (s *svc) Options() router.Options { + return s.opts +} + +func (s *svc) Table() router.Table { + return s.table +} + +func (s *svc) advertiseEvents(advertChan chan *router.Advert, stream pb.Router_AdvertiseService) error { + go func() { + <-s.exit + stream.Close() + }() + + var advErr error + + for { + resp, err := stream.Recv() + if err != nil { + if err != io.EOF { + advErr = err + } + break + } + + events := make([]*router.Event, len(resp.Events)) + for i, event := range resp.Events { + route := router.Route{ + Service: event.Route.Service, + Address: event.Route.Address, + Gateway: event.Route.Gateway, + Network: event.Route.Network, + Link: event.Route.Link, + Metric: int(event.Route.Metric), + } + + events[i] = &router.Event{ + Type: router.EventType(event.Type), + Timestamp: time.Unix(0, event.Timestamp), + Route: route, + } + } + + advert := &router.Advert{ + Id: resp.Id, + Type: router.AdvertType(resp.Type), + Timestamp: time.Unix(0, resp.Timestamp), + TTL: time.Duration(resp.Ttl), + Events: events, + } + + select { + case advertChan <- advert: + case <-s.exit: + close(advertChan) + return nil + } + } + + // close the channel on exit + close(advertChan) + + return advErr +} + +// Advertise advertises routes to the network +func (s *svc) Advertise() (<-chan *router.Advert, error) { + s.Lock() + defer s.Unlock() + + // get the status + status := s.Status() + + switch status.Code { + case router.Running, router.Advertising: + stream, err := s.router.Advertise(context.Background(), &pb.Request{}, s.callOpts...) + if err != nil { + return nil, fmt.Errorf("failed getting advert stream: %s", err) + } + // create advertise and event channels + advertChan := make(chan *router.Advert) + go s.advertiseEvents(advertChan, stream) + return advertChan, nil + case router.Stopped: + // check if our router is stopped + select { + case <-s.exit: + s.exit = make(chan bool) + // call advertise again + return s.Advertise() + default: + return nil, fmt.Errorf("not running") + } + } + + return nil, fmt.Errorf("error: %s", s.status.Error) +} + +// Process processes incoming adverts +func (s *svc) Process(advert *router.Advert) error { + var events []*pb.Event + for _, event := range advert.Events { + route := &pb.Route{ + Service: event.Route.Service, + Address: event.Route.Address, + Gateway: event.Route.Gateway, + Network: event.Route.Network, + Link: event.Route.Link, + Metric: int64(event.Route.Metric), + } + e := &pb.Event{ + Type: pb.EventType(event.Type), + Timestamp: event.Timestamp.UnixNano(), + Route: route, + } + events = append(events, e) + } + + advertReq := &pb.Advert{ + Id: s.Options().Id, + Type: pb.AdvertType(advert.Type), + Timestamp: advert.Timestamp.UnixNano(), + Events: events, + } + + if _, err := s.router.Process(context.Background(), advertReq, s.callOpts...); err != nil { + return err + } + + return nil +} + +// Status returns router status +func (s *svc) Status() router.Status { + s.Lock() + defer s.Unlock() + + // check if its stopped + select { + case <-s.exit: + return router.Status{ + Code: router.Stopped, + Error: nil, + } + default: + // don't block + } + + // check the remote router + rsp, err := s.router.Status(context.Background(), &pb.Request{}, s.callOpts...) + if err != nil { + return router.Status{ + Code: router.Error, + Error: err, + } + } + + code := router.Running + var serr error + + switch rsp.Status.Code { + case "running": + code = router.Running + case "advertising": + code = router.Advertising + case "stopped": + code = router.Stopped + case "error": + code = router.Error + } + + if len(rsp.Status.Error) > 0 { + serr = errors.New(rsp.Status.Error) + } + + return router.Status{ + Code: code, + Error: serr, + } +} + +// Remote router cannot be stopped +func (s *svc) Stop() error { + s.Lock() + defer s.Unlock() + + select { + case <-s.exit: + return nil + default: + close(s.exit) + } + + return nil +} + +// Lookup looks up routes in the routing table and returns them +func (s *svc) Lookup(q router.Query) ([]router.Route, error) { + // call the router + resp, err := s.router.Lookup(context.Background(), &pb.LookupRequest{ + Query: &pb.Query{ + Service: q.Options().Service, + Gateway: q.Options().Gateway, + Network: q.Options().Network, + }, + }, s.callOpts...) + + // errored out + if err != nil { + return nil, err + } + + routes := make([]router.Route, len(resp.Routes)) + for i, route := range resp.Routes { + routes[i] = router.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int(route.Metric), + } + } + + return routes, nil +} + +// Watch returns a watcher which allows to track updates to the routing table +func (s *svc) Watch(opts ...router.WatchOption) (router.Watcher, error) { + rsp, err := s.router.Watch(context.Background(), &pb.WatchRequest{}, s.callOpts...) + if err != nil { + return nil, err + } + var options router.WatchOptions + for _, o := range opts { + o(&options) + } + return newWatcher(rsp, options) +} + +// Returns the router implementation +func (s *svc) String() string { + return "service" +} diff --git a/router/service/table.go b/router/service/table.go new file mode 100644 index 00000000..a6c44fd3 --- /dev/null +++ b/router/service/table.go @@ -0,0 +1,121 @@ +package service + +import ( + "context" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/router" + pb "github.com/micro/go-micro/router/proto" +) + +type table struct { + table pb.TableService + callOpts []client.CallOption +} + +// Create new route in the routing table +func (t *table) Create(r router.Route) error { + route := &pb.Route{ + Service: r.Service, + Address: r.Address, + Gateway: r.Gateway, + Network: r.Network, + Link: r.Link, + Metric: int64(r.Metric), + } + + if _, err := t.table.Create(context.Background(), route, t.callOpts...); err != nil { + return err + } + + return nil +} + +// Delete deletes existing route from the routing table +func (t *table) Delete(r router.Route) error { + route := &pb.Route{ + Service: r.Service, + Address: r.Address, + Gateway: r.Gateway, + Network: r.Network, + Link: r.Link, + Metric: int64(r.Metric), + } + + if _, err := t.table.Delete(context.Background(), route, t.callOpts...); err != nil { + return err + } + + return nil +} + +// Update updates route in the routing table +func (t *table) Update(r router.Route) error { + route := &pb.Route{ + Service: r.Service, + Address: r.Address, + Gateway: r.Gateway, + Network: r.Network, + Link: r.Link, + Metric: int64(r.Metric), + } + + if _, err := t.table.Update(context.Background(), route, t.callOpts...); err != nil { + return err + } + + return nil +} + +// List returns the list of all routes in the table +func (t *table) List() ([]router.Route, error) { + resp, err := t.table.List(context.Background(), &pb.Request{}, t.callOpts...) + if err != nil { + return nil, err + } + + routes := make([]router.Route, len(resp.Routes)) + for i, route := range resp.Routes { + routes[i] = router.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int(route.Metric), + } + } + + return routes, nil +} + +// Lookup looks up routes in the routing table and returns them +func (t *table) Query(q router.Query) ([]router.Route, error) { + // call the router + resp, err := t.table.Query(context.Background(), &pb.QueryRequest{ + Query: &pb.Query{ + Service: q.Options().Service, + Gateway: q.Options().Gateway, + Network: q.Options().Network, + }, + }, t.callOpts...) + + // errored out + if err != nil { + return nil, err + } + + routes := make([]router.Route, len(resp.Routes)) + for i, route := range resp.Routes { + routes[i] = router.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Link: route.Link, + Metric: int(route.Metric), + } + } + + return routes, nil +} diff --git a/router/service/watcher.go b/router/service/watcher.go new file mode 100644 index 00000000..f0fb3d5c --- /dev/null +++ b/router/service/watcher.go @@ -0,0 +1,117 @@ +package service + +import ( + "io" + "sync" + "time" + + "github.com/micro/go-micro/router" + pb "github.com/micro/go-micro/router/proto" +) + +type watcher struct { + sync.RWMutex + opts router.WatchOptions + resChan chan *router.Event + done chan struct{} +} + +func newWatcher(rsp pb.Router_WatchService, opts router.WatchOptions) (*watcher, error) { + w := &watcher{ + opts: opts, + resChan: make(chan *router.Event), + done: make(chan struct{}), + } + + go func() { + for { + select { + case <-w.done: + return + default: + if err := w.watch(rsp); err != nil { + w.Stop() + return + } + } + } + }() + + return w, nil +} + +// watchRouter watches router and send events to all registered watchers +func (w *watcher) watch(stream pb.Router_WatchService) error { + defer stream.Close() + + var watchErr error + + for { + resp, err := stream.Recv() + if err != nil { + if err != io.EOF { + watchErr = err + } + break + } + + route := router.Route{ + Service: resp.Route.Service, + Address: resp.Route.Address, + Gateway: resp.Route.Gateway, + Network: resp.Route.Network, + Link: resp.Route.Link, + Metric: int(resp.Route.Metric), + } + + event := &router.Event{ + Type: router.EventType(resp.Type), + Timestamp: time.Unix(0, resp.Timestamp), + Route: route, + } + + for { + select { + case w.resChan <- event: + case <-w.done: + } + } + } + + return watchErr +} + +// Next is a blocking call that returns watch result +func (w *watcher) Next() (*router.Event, error) { + for { + select { + case res := <-w.resChan: + switch w.opts.Service { + case res.Route.Service, "*": + return res, nil + default: + continue + } + case <-w.done: + return nil, router.ErrWatcherStopped + } + } +} + +// Chan returns event channel +func (w *watcher) Chan() (<-chan *router.Event, error) { + return w.resChan, nil +} + +// Stop stops watcher +func (w *watcher) Stop() { + w.Lock() + defer w.Unlock() + + select { + case <-w.done: + return + default: + close(w.done) + } +} diff --git a/router/table.go b/router/table.go new file mode 100644 index 00000000..6b34bb00 --- /dev/null +++ b/router/table.go @@ -0,0 +1,203 @@ +package router + +import ( + "errors" + "sync" + "time" + + "github.com/google/uuid" +) + +// table is an in memory routing table +type table struct { + sync.RWMutex + // routes stores service routes + routes map[string]map[uint64]Route + // watchers stores table watchers + watchers map[string]*tableWatcher +} + +// newtable creates a new routing table and returns it +func newTable(opts ...Option) *table { + return &table{ + routes: make(map[string]map[uint64]Route), + watchers: make(map[string]*tableWatcher), + } +} + +// Create creates new route in the routing table +func (t *table) Create(r Route) error { + service := r.Service + sum := r.Hash() + + t.Lock() + defer t.Unlock() + + // check if there are any routes in the table for the route destination + if _, ok := t.routes[service]; !ok { + t.routes[service] = make(map[uint64]Route) + t.routes[service][sum] = r + go t.sendEvent(&Event{Type: Create, Timestamp: time.Now(), Route: r}) + return nil + } + + // add new route to the table for the route destination + if _, ok := t.routes[service][sum]; !ok { + t.routes[service][sum] = r + go t.sendEvent(&Event{Type: Create, Timestamp: time.Now(), Route: r}) + return nil + } + + return ErrDuplicateRoute +} + +// Delete deletes the route from the routing table +func (t *table) Delete(r Route) error { + service := r.Service + sum := r.Hash() + + t.Lock() + defer t.Unlock() + + if _, ok := t.routes[service]; !ok { + return ErrRouteNotFound + } + + delete(t.routes[service], sum) + go t.sendEvent(&Event{Type: Delete, Timestamp: time.Now(), Route: r}) + + return nil +} + +// Update updates routing table with the new route +func (t *table) Update(r Route) error { + service := r.Service + sum := r.Hash() + + t.Lock() + defer t.Unlock() + + // check if the route destination has any routes in the table + if _, ok := t.routes[service]; !ok { + t.routes[service] = make(map[uint64]Route) + t.routes[service][sum] = r + go t.sendEvent(&Event{Type: Create, Timestamp: time.Now(), Route: r}) + return nil + } + + t.routes[service][sum] = r + go t.sendEvent(&Event{Type: Update, Timestamp: time.Now(), Route: r}) + + return nil +} + +// List returns a list of all routes in the table +func (t *table) List() ([]Route, error) { + t.RLock() + defer t.RUnlock() + + var routes []Route + for _, rmap := range t.routes { + for _, route := range rmap { + routes = append(routes, route) + } + } + + return routes, nil +} + +// isMatch checks if the route matches given network and router +func isMatch(route Route, network, router string) bool { + if network == "*" || network == route.Network { + if router == "*" || router == route.Gateway { + return true + } + } + return false +} + +// findRoutes finds all the routes for given network and router and returns them +func findRoutes(routes map[uint64]Route, network, router string) []Route { + var results []Route + for _, route := range routes { + if isMatch(route, network, router) { + results = append(results, route) + } + } + return results +} + +// Lookup queries routing table and returns all routes that match the lookup query +func (t *table) Query(q Query) ([]Route, error) { + t.RLock() + defer t.RUnlock() + + if q.Options().Service != "*" { + if _, ok := t.routes[q.Options().Service]; !ok { + return nil, ErrRouteNotFound + } + return findRoutes(t.routes[q.Options().Service], q.Options().Network, q.Options().Gateway), nil + } + + var results []Route + // search through all destinations + for _, routes := range t.routes { + results = append(results, findRoutes(routes, q.Options().Network, q.Options().Gateway)...) + } + + return results, nil +} + +// Watch returns routing table entry watcher +func (t *table) Watch(opts ...WatchOption) (Watcher, error) { + // by default watch everything + wopts := WatchOptions{ + Service: "*", + } + + for _, o := range opts { + o(&wopts) + } + + w := &tableWatcher{ + id: uuid.New().String(), + opts: wopts, + resChan: make(chan *Event, 10), + done: make(chan struct{}), + } + + // when the watcher is stopped delete it + go func() { + <-w.done + t.Lock() + delete(t.watchers, w.id) + t.Unlock() + }() + + // save the watcher + t.Lock() + t.watchers[w.id] = w + t.Unlock() + + return w, nil +} + +// sendEvent sends events to all subscribed watchers +func (t *table) sendEvent(e *Event) { + t.RLock() + defer t.RUnlock() + + for _, w := range t.watchers { + select { + case w.resChan <- e: + case <-w.done: + } + } +} + +var ( + // ErrRouteNotFound is returned when no route was found in the routing table + ErrRouteNotFound = errors.New("route not found") + // ErrDuplicateRoute is returned when the route already exists + ErrDuplicateRoute = errors.New("duplicate route") +) diff --git a/router/table_test.go b/router/table_test.go new file mode 100644 index 00000000..16c73f2f --- /dev/null +++ b/router/table_test.go @@ -0,0 +1,194 @@ +package router + +import "testing" + +func testSetup() (*table, Route) { + table := newTable() + + route := Route{ + Service: "dest.svc", + Gateway: "dest.gw", + Network: "dest.network", + Link: "det.link", + Metric: 10, + } + + return table, route +} + +func TestCreate(t *testing.T) { + table, route := testSetup() + + if err := table.Create(route); err != nil { + t.Errorf("error adding route: %s", err) + } + + // adds new route for the original destination + route.Gateway = "dest.gw2" + + if err := table.Create(route); err != nil { + t.Errorf("error adding route: %s", err) + } + + // adding the same route under Insert policy must error + if err := table.Create(route); err != ErrDuplicateRoute { + t.Errorf("error adding route. Expected error: %s, found: %s", ErrDuplicateRoute, err) + } +} + +func TestDelete(t *testing.T) { + table, route := testSetup() + + if err := table.Create(route); err != nil { + t.Errorf("error adding route: %s", err) + } + + // should fail to delete non-existant route + prevSvc := route.Service + route.Service = "randDest" + + if err := table.Delete(route); err != ErrRouteNotFound { + t.Errorf("error deleting route. Expected: %s, found: %s", ErrRouteNotFound, err) + } + + // we should be able to delete the existing route + route.Service = prevSvc + + if err := table.Delete(route); err != nil { + t.Errorf("error deleting route: %s", err) + } +} + +func TestUpdate(t *testing.T) { + table, route := testSetup() + + if err := table.Create(route); err != nil { + t.Errorf("error adding route: %s", err) + } + + // change the metric of the original route + route.Metric = 200 + + if err := table.Update(route); err != nil { + t.Errorf("error updating route: %s", err) + } + + // this should add a new route + route.Service = "rand.dest" + + if err := table.Update(route); err != nil { + t.Errorf("error updating route: %s", err) + } +} + +func TestList(t *testing.T) { + table, route := testSetup() + + svc := []string{"one.svc", "two.svc", "three.svc"} + + for i := 0; i < len(svc); i++ { + route.Service = svc[i] + if err := table.Create(route); err != nil { + t.Errorf("error adding route: %s", err) + } + } + + routes, err := table.List() + if err != nil { + t.Errorf("error listing routes: %s", err) + } + + if len(routes) != len(svc) { + t.Errorf("incorrect number of routes listed. Expected: %d, found: %d", len(svc), len(routes)) + } +} + +func TestQuery(t *testing.T) { + table, route := testSetup() + + svc := []string{"svc1", "svc2", "svc3"} + net := []string{"net1", "net2", "net1"} + gw := []string{"gw1", "gw2", "gw3"} + + for i := 0; i < len(svc); i++ { + route.Service = svc[i] + route.Network = net[i] + route.Gateway = gw[i] + if err := table.Create(route); err != nil { + t.Errorf("error adding route: %s", err) + } + } + + // return all routes + query := NewQuery() + + routes, err := table.Query(query) + if err != nil { + t.Errorf("error looking up routes: %s", err) + } + + // query particular net + query = NewQuery(QueryNetwork("net1")) + + routes, err = table.Query(query) + if err != nil { + t.Errorf("error looking up routes: %s", err) + } + + if len(routes) != 2 { + t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 2, len(routes)) + } + + // query particular gateway + gateway := "gw1" + query = NewQuery(QueryGateway(gateway)) + + routes, err = table.Query(query) + if err != nil { + t.Errorf("error looking up routes: %s", err) + } + + if len(routes) != 1 { + t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes)) + } + + if routes[0].Gateway != gateway { + t.Errorf("incorrect route returned. Expected gateway: %s, found: %s", gateway, routes[0].Gateway) + } + + // query particular route + network := "net1" + query = NewQuery( + QueryGateway(gateway), + QueryNetwork(network), + ) + + routes, err = table.Query(query) + if err != nil { + t.Errorf("error looking up routes: %s", err) + } + + if len(routes) != 1 { + t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes)) + } + + if routes[0].Gateway != gateway { + t.Errorf("incorrect route returned. Expected gateway: %s, found: %s", gateway, routes[0].Gateway) + } + + if routes[0].Network != network { + t.Errorf("incorrect network returned. Expected network: %s, found: %s", network, routes[0].Network) + } + + // bullshit route query + query = NewQuery(QueryService("foobar")) + + routes, err = table.Query(query) + if err != ErrRouteNotFound { + t.Errorf("error looking up routes. Expected: %s, found: %s", ErrRouteNotFound, err) + } + + if len(routes) != 0 { + t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 0, len(routes)) + } +} diff --git a/router/watcher.go b/router/watcher.go new file mode 100644 index 00000000..f2c8beac --- /dev/null +++ b/router/watcher.go @@ -0,0 +1,120 @@ +package router + +import ( + "errors" + "sync" + "time" +) + +// EventType defines routing table event +type EventType int + +const ( + // Create is emitted when a new route has been created + Create EventType = iota + // Delete is emitted when an existing route has been deleted + Delete + // Update is emitted when an existing route has been updated + Update +) + +// String returns human readable event type +func (t EventType) String() string { + switch t { + case Create: + return "create" + case Delete: + return "delete" + case Update: + return "update" + default: + return "unknown" + } +} + +// Event is returned by a call to Next on the watcher. +type Event struct { + // Type defines type of event + Type EventType + // Timestamp is event timestamp + Timestamp time.Time + // Route is table route + Route Route +} + +// WatchOption is used to define what routes to watch in the table +type WatchOption func(*WatchOptions) + +// Watcher defines routing table watcher interface +// Watcher returns updates to the routing table +type Watcher interface { + // Next is a blocking call that returns watch result + Next() (*Event, error) + // Chan returns event channel + Chan() (<-chan *Event, error) + // Stop stops watcher + Stop() +} + +// WatchOptions are table watcher options +type WatchOptions struct { + // Service allows to watch specific service routes + Service string +} + +// WatchService sets what service routes to watch +// Service is the microservice name +func WatchService(s string) WatchOption { + return func(o *WatchOptions) { + o.Service = s + } +} + +type tableWatcher struct { + sync.RWMutex + id string + opts WatchOptions + resChan chan *Event + done chan struct{} +} + +// Next returns the next noticed action taken on table +// TODO: right now we only allow to watch particular service +func (w *tableWatcher) Next() (*Event, error) { + for { + select { + case res := <-w.resChan: + switch w.opts.Service { + case res.Route.Service, "*": + return res, nil + default: + continue + } + case <-w.done: + return nil, ErrWatcherStopped + } + } +} + +// Chan returns watcher events channel +func (w *tableWatcher) Chan() (<-chan *Event, error) { + return w.resChan, nil +} + +// Stop stops routing table watcher +func (w *tableWatcher) Stop() { + w.Lock() + defer w.Unlock() + + select { + case <-w.done: + return + default: + close(w.done) + } +} + +var ( + // ErrWatcherStopped is returned when routing table watcher has been stopped + ErrWatcherStopped = errors.New("watcher stopped") +) diff --git a/server/buffer.go b/server/buffer.go deleted file mode 100644 index 4df03c27..00000000 --- a/server/buffer.go +++ /dev/null @@ -1,14 +0,0 @@ -package server - -import ( - "bytes" -) - -type buffer struct { - *bytes.Buffer -} - -func (b *buffer) Close() error { - b.Buffer.Reset() - return nil -} diff --git a/server/debug.go b/server/debug.go deleted file mode 100644 index 18225cf7..00000000 --- a/server/debug.go +++ /dev/null @@ -1,14 +0,0 @@ -package server - -import ( - "github.com/micro/go-micro/server/debug" -) - -// We use this to wrap any debug handlers so we preserve the signature Debug.{Method} -type Debug struct { - debug.DebugHandler -} - -func registerDebugHandler(s Server) { - s.Handle(s.NewHandler(&Debug{s.Options().DebugHandler}, InternalHandler(true))) -} diff --git a/server/debug/debug.go b/server/debug/debug.go deleted file mode 100644 index f185209d..00000000 --- a/server/debug/debug.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - "runtime" - "time" - - proto "github.com/micro/go-micro/server/debug/proto" -) - -// The debug handler represents an internal server handler -// used to determine health, status and env info about -// a service node. It's akin to Google's /statusz, /healthz, -// and /varz -type DebugHandler interface { - Health(ctx context.Context, req *proto.HealthRequest, rsp *proto.HealthResponse) error - Stats(ctx context.Context, req *proto.StatsRequest, rsp *proto.StatsResponse) error -} - -// Our own internal handler -type debug struct { - started int64 -} - -var ( - DefaultDebugHandler DebugHandler = newDebug() -) - -func newDebug() *debug { - return &debug{ - started: time.Now().Unix(), - } -} - -func (d *debug) Health(ctx context.Context, req *proto.HealthRequest, rsp *proto.HealthResponse) error { - rsp.Status = "ok" - return nil -} - -func (d *debug) Stats(ctx context.Context, req *proto.StatsRequest, rsp *proto.StatsResponse) error { - var mstat runtime.MemStats - runtime.ReadMemStats(&mstat) - - rsp.Started = uint64(d.started) - rsp.Uptime = uint64(time.Now().Unix() - d.started) - rsp.Memory = mstat.Alloc - rsp.Gc = mstat.PauseTotalNs - rsp.Threads = uint64(runtime.NumGoroutine()) - return nil -} diff --git a/server/debug/proto/debug.pb.go b/server/debug/proto/debug.pb.go deleted file mode 100644 index 7cfa7d5e..00000000 --- a/server/debug/proto/debug.pb.go +++ /dev/null @@ -1,100 +0,0 @@ -// Code generated by protoc-gen-go. -// source: github.com/micro/go-micro/server/debug/proto/debug.proto -// DO NOT EDIT! - -/* -Package debug is a generated protocol buffer package. - -It is generated from these files: - github.com/micro/go-micro/server/debug/proto/debug.proto - -It has these top-level messages: - HealthRequest - HealthResponse - StatsRequest - StatsResponse -*/ -package debug - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import 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.ProtoPackageIsVersion2 // please upgrade the proto package - -type HealthRequest struct { -} - -func (m *HealthRequest) Reset() { *m = HealthRequest{} } -func (m *HealthRequest) String() string { return proto.CompactTextString(m) } -func (*HealthRequest) ProtoMessage() {} -func (*HealthRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -type HealthResponse struct { - // default: ok - Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` -} - -func (m *HealthResponse) Reset() { *m = HealthResponse{} } -func (m *HealthResponse) String() string { return proto.CompactTextString(m) } -func (*HealthResponse) ProtoMessage() {} -func (*HealthResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -type StatsRequest struct { -} - -func (m *StatsRequest) Reset() { *m = StatsRequest{} } -func (m *StatsRequest) String() string { return proto.CompactTextString(m) } -func (*StatsRequest) ProtoMessage() {} -func (*StatsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } - -type StatsResponse struct { - // unix timestamp - Started uint64 `protobuf:"varint,1,opt,name=started" json:"started,omitempty"` - // in seconds - Uptime uint64 `protobuf:"varint,2,opt,name=uptime" json:"uptime,omitempty"` - // in bytes - Memory uint64 `protobuf:"varint,3,opt,name=memory" json:"memory,omitempty"` - // num threads - Threads uint64 `protobuf:"varint,4,opt,name=threads" json:"threads,omitempty"` - // in nanoseconds - Gc uint64 `protobuf:"varint,5,opt,name=gc" json:"gc,omitempty"` -} - -func (m *StatsResponse) Reset() { *m = StatsResponse{} } -func (m *StatsResponse) String() string { return proto.CompactTextString(m) } -func (*StatsResponse) ProtoMessage() {} -func (*StatsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } - -func init() { - proto.RegisterType((*HealthRequest)(nil), "HealthRequest") - proto.RegisterType((*HealthResponse)(nil), "HealthResponse") - proto.RegisterType((*StatsRequest)(nil), "StatsRequest") - proto.RegisterType((*StatsResponse)(nil), "StatsResponse") -} - -var fileDescriptor0 = []byte{ - // 201 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x34, 0x8f, 0xbd, 0x6e, 0x83, 0x30, - 0x14, 0x85, 0x05, 0xa5, 0x54, 0xbd, 0x2a, 0x54, 0x62, 0xa8, 0x3c, 0x56, 0x4c, 0x2c, 0xc5, 0x43, - 0x97, 0x3e, 0x42, 0x67, 0xf2, 0x04, 0xfc, 0x5c, 0x19, 0xa4, 0x38, 0x26, 0xbe, 0xd7, 0x91, 0x32, - 0xe7, 0xc5, 0x03, 0xb6, 0xd9, 0xce, 0xf7, 0xd9, 0xe7, 0x48, 0x17, 0xfe, 0xd4, 0xc2, 0xb3, 0x1b, - 0xda, 0xd1, 0x68, 0xa9, 0x97, 0xd1, 0x1a, 0xa9, 0xcc, 0x4f, 0x08, 0x84, 0xf6, 0x86, 0x56, 0x4e, - 0x38, 0x38, 0x25, 0x57, 0x6b, 0xd8, 0x84, 0xdc, 0xfa, 0x5c, 0x7f, 0x42, 0xf1, 0x8f, 0xfd, 0x99, - 0xe7, 0x0e, 0xaf, 0x0e, 0x89, 0xeb, 0x06, 0xca, 0x43, 0xd0, 0x6a, 0x2e, 0x84, 0xd5, 0x17, 0xe4, - 0xc4, 0x3d, 0x3b, 0x12, 0xc9, 0x77, 0xd2, 0xbc, 0x77, 0x91, 0xea, 0x12, 0x3e, 0x4e, 0x5b, 0xa2, - 0xa3, 0xf9, 0x48, 0xa0, 0x88, 0x22, 0x36, 0x05, 0xbc, 0x6d, 0x7f, 0x2d, 0xe3, 0xe4, 0xab, 0x59, - 0x77, 0xe0, 0xbe, 0xe9, 0x56, 0x5e, 0x34, 0x8a, 0xd4, 0x3f, 0x44, 0xda, 0xbd, 0x46, 0x6d, 0xec, - 0x5d, 0xbc, 0x04, 0x1f, 0x68, 0x5f, 0xe2, 0xd9, 0x62, 0x3f, 0x91, 0xc8, 0xc2, 0x52, 0xc4, 0xaa, - 0x84, 0x54, 0x8d, 0xe2, 0xd5, 0xcb, 0x2d, 0x0d, 0xb9, 0xbf, 0xeb, 0xf7, 0x19, 0x00, 0x00, 0xff, - 0xff, 0xc6, 0x75, 0x51, 0x35, 0x13, 0x01, 0x00, 0x00, -} diff --git a/server/extractor.go b/server/extractor.go index 0a4297e4..52ce0056 100644 --- a/server/extractor.go +++ b/server/extractor.go @@ -20,10 +20,6 @@ func extractValue(v reflect.Type, d int) *registry.Value { v = v.Elem() } - if len(v.Name()) == 0 { - return nil - } - arg := ®istry.Value{ Name: v.Name(), Type: v.Name(), @@ -65,10 +61,6 @@ func extractValue(v reflect.Type, d int) *registry.Value { p = p.Elem() } arg.Type = "[]" + p.Name() - val := extractValue(v.Elem(), d+1) - if val != nil { - arg.Values = append(arg.Values, val) - } } return arg diff --git a/server/grpc/codec.go b/server/grpc/codec.go index 50a96ef4..53de36c3 100644 --- a/server/grpc/codec.go +++ b/server/grpc/codec.go @@ -3,17 +3,27 @@ package grpc import ( "encoding/json" "fmt" + "strings" + b "bytes" + + "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/bytes" "github.com/micro/go-micro/codec/jsonrpc" "github.com/micro/go-micro/codec/protorpc" + "google.golang.org/grpc" "google.golang.org/grpc/encoding" + "google.golang.org/grpc/metadata" ) type jsonCodec struct{} type bytesCodec struct{} type protoCodec struct{} +type wrapCodec struct{ encoding.Codec } + +var jsonpbMarshaler = &jsonpb.Marshaler{} var ( defaultGRPCCodecs = map[string]encoding.Codec{ @@ -36,6 +46,27 @@ var ( } ) +func (w wrapCodec) String() string { + return w.Codec.Name() +} + +func (w wrapCodec) Marshal(v interface{}) ([]byte, error) { + b, ok := v.(*bytes.Frame) + if ok { + return b.Data, nil + } + return w.Codec.Marshal(v) +} + +func (w wrapCodec) Unmarshal(data []byte, v interface{}) error { + b, ok := v.(*bytes.Frame) + if ok { + b.Data = data + return nil + } + return w.Codec.Unmarshal(data, v) +} + func (protoCodec) Marshal(v interface{}) ([]byte, error) { return proto.Marshal(v.(proto.Message)) } @@ -49,10 +80,20 @@ func (protoCodec) Name() string { } func (jsonCodec) Marshal(v interface{}) ([]byte, error) { + if pb, ok := v.(proto.Message); ok { + s, err := jsonpbMarshaler.MarshalToString(pb) + + return []byte(s), err + } + return json.Marshal(v) } func (jsonCodec) Unmarshal(data []byte, v interface{}) error { + if pb, ok := v.(proto.Message); ok { + return jsonpb.Unmarshal(b.NewReader(data), pb) + } + return json.Unmarshal(data, v) } @@ -80,3 +121,61 @@ func (bytesCodec) Unmarshal(data []byte, v interface{}) error { func (bytesCodec) Name() string { return "bytes" } + +type grpcCodec struct { + // headers + id string + target string + method string + endpoint string + + s grpc.ServerStream + c encoding.Codec +} + +func (g *grpcCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error { + md, _ := metadata.FromIncomingContext(g.s.Context()) + if m == nil { + m = new(codec.Message) + } + if m.Header == nil { + m.Header = make(map[string]string) + } + for k, v := range md { + m.Header[k] = strings.Join(v, ",") + } + m.Id = g.id + m.Target = g.target + m.Method = g.method + m.Endpoint = g.endpoint + return nil +} + +func (g *grpcCodec) ReadBody(v interface{}) error { + // caller has requested a frame + if f, ok := v.(*bytes.Frame); ok { + return g.s.RecvMsg(f) + } + return g.s.RecvMsg(v) +} + +func (g *grpcCodec) Write(m *codec.Message, v interface{}) error { + // if we don't have a body + if v != nil { + b, err := g.c.Marshal(v) + if err != nil { + return err + } + m.Body = b + } + // write the body using the framing codec + return g.s.SendMsg(&bytes.Frame{m.Body}) +} + +func (g *grpcCodec) Close() error { + return nil +} + +func (g *grpcCodec) String() string { + return g.c.Name() +} diff --git a/server/grpc/debug.go b/server/grpc/debug.go deleted file mode 100644 index 5f235346..00000000 --- a/server/grpc/debug.go +++ /dev/null @@ -1,15 +0,0 @@ -package grpc - -import ( - "github.com/micro/go-micro/server" - "github.com/micro/go-micro/server/debug" -) - -// We use this to wrap any debug handlers so we preserve the signature Debug.{Method} -type Debug struct { - debug.DebugHandler -} - -func registerDebugHandler(s server.Server) { - s.Handle(s.NewHandler(&Debug{s.Options().DebugHandler}, server.InternalHandler(true))) -} diff --git a/server/grpc/extractor.go b/server/grpc/extractor.go index 89c00ce9..753cc175 100644 --- a/server/grpc/extractor.go +++ b/server/grpc/extractor.go @@ -56,10 +56,6 @@ func extractValue(v reflect.Type, d int) *registry.Value { p = p.Elem() } arg.Type = "[]" + p.Name() - val := extractValue(v.Elem(), d+1) - if val != nil { - arg.Values = append(arg.Values, val) - } } return arg diff --git a/server/grpc/grpc.go b/server/grpc/grpc.go index bde2e4c6..2aefc6f1 100644 --- a/server/grpc/grpc.go +++ b/server/grpc/grpc.go @@ -22,12 +22,14 @@ import ( "github.com/micro/go-micro/util/addr" mgrpc "github.com/micro/go-micro/util/grpc" "github.com/micro/go-micro/util/log" + mnet "github.com/micro/go-micro/util/net" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" "google.golang.org/grpc/status" ) @@ -56,8 +58,9 @@ type grpcServer struct { } func init() { - encoding.RegisterCodec(jsonCodec{}) - encoding.RegisterCodec(bytesCodec{}) + encoding.RegisterCodec(wrapCodec{jsonCodec{}}) + encoding.RegisterCodec(wrapCodec{protoCodec{}}) + encoding.RegisterCodec(wrapCodec{bytesCodec{}}) } func newGRPCServer(opts ...server.Option) server.Server { @@ -202,6 +205,12 @@ func (g *grpcServer) handler(srv interface{}, stream grpc.ServerStream) error { // create new context ctx := meta.NewContext(stream.Context(), md) + // get peer from context + if p, ok := peer.FromContext(stream.Context()); ok { + md["Remote"] = p.Addr.String() + ctx = peer.NewContext(ctx, p) + } + // set the timeout if we have it if len(to) > 0 { if n, err := strconv.ParseUint(to, 10, 64); err == nil { @@ -211,14 +220,30 @@ func (g *grpcServer) handler(srv interface{}, stream grpc.ServerStream) error { // process via router if g.opts.Router != nil { - // create a client.Request - request := &rpcRequest{ - service: g.opts.Name, - contentType: ct, - method: fmt.Sprintf("%s.%s", serviceName, methodName), + cc, err := g.newGRPCCodec(ct) + if err != nil { + return errors.InternalServerError("go.micro.server", err.Error()) + } + codec := &grpcCodec{ + method: fmt.Sprintf("%s.%s", serviceName, methodName), + endpoint: fmt.Sprintf("%s.%s", serviceName, methodName), + target: g.opts.Name, + s: stream, + c: cc, } - response := &rpcResponse{} + // create a client.Request + request := &rpcRequest{ + service: mgrpc.ServiceFromMethod(fullMethod), + contentType: ct, + method: fmt.Sprintf("%s.%s", serviceName, methodName), + codec: codec, + } + + response := &rpcResponse{ + header: make(map[string]string), + codec: codec, + } // create a wrapped function handler := func(ctx context.Context, req server.Request, rsp interface{}) error { @@ -480,10 +505,11 @@ func (g *grpcServer) Subscribe(sb server.Subscriber) error { } func (g *grpcServer) Register() error { + var err error + var advt, host, port string + // parse address for host, port config := g.opts - var advt, host string - var port int // check the advertise address first // if it exists then use it, otherwise @@ -494,12 +520,14 @@ func (g *grpcServer) Register() error { advt = config.Address } - parts := strings.Split(advt, ":") - if len(parts) > 1 { - host = strings.Join(parts[:len(parts)-1], ":") - port, _ = strconv.Atoi(parts[len(parts)-1]) + if cnt := strings.Count(advt, ":"); cnt >= 1 { + // ipv6 address in format [host]:port or ipv4 host:port + host, port, err = net.SplitHostPort(advt) + if err != nil { + return err + } } else { - host = parts[0] + host = advt } addr, err := addr.Extract(host) @@ -510,8 +538,7 @@ func (g *grpcServer) Register() error { // register service node := ®istry.Node{ Id: config.Name + "-" + config.Id, - Address: addr, - Port: port, + Address: mnet.HostPort(addr, port), Metadata: config.Metadata, } @@ -606,9 +633,10 @@ func (g *grpcServer) Register() error { } func (g *grpcServer) Deregister() error { + var err error + var advt, host, port string + config := g.opts - var advt, host string - var port int // check the advertise address first // if it exists then use it, otherwise @@ -619,12 +647,14 @@ func (g *grpcServer) Deregister() error { advt = config.Address } - parts := strings.Split(advt, ":") - if len(parts) > 1 { - host = strings.Join(parts[:len(parts)-1], ":") - port, _ = strconv.Atoi(parts[len(parts)-1]) + if cnt := strings.Count(advt, ":"); cnt >= 1 { + // ipv6 address in format [host]:port or ipv4 host:port + host, port, err = net.SplitHostPort(advt) + if err != nil { + return err + } } else { - host = parts[0] + host = advt } addr, err := addr.Extract(host) @@ -634,8 +664,7 @@ func (g *grpcServer) Deregister() error { node := ®istry.Node{ Id: config.Name + "-" + config.Id, - Address: addr, - Port: port, + Address: mnet.HostPort(addr, port), } service := ®istry.Service{ @@ -671,7 +700,6 @@ func (g *grpcServer) Deregister() error { } func (g *grpcServer) Start() error { - registerDebugHandler(g) config := g.opts // micro: config.Transport.Listen(config.Address) @@ -690,7 +718,10 @@ func (g *grpcServer) Start() error { return err } - log.Logf("Broker [%s] Listening on %s", config.Broker.String(), config.Broker.Address()) + baddr := config.Broker.Address() + bname := config.Broker.String() + + log.Logf("Broker [%s] Connected to %s", bname, baddr) // announce self to the world if err := g.Register(); err != nil { diff --git a/server/grpc/options.go b/server/grpc/options.go index 65d82fcc..c57c022e 100644 --- a/server/grpc/options.go +++ b/server/grpc/options.go @@ -8,7 +8,6 @@ import ( "github.com/micro/go-micro/codec" "github.com/micro/go-micro/registry" "github.com/micro/go-micro/server" - "github.com/micro/go-micro/server/debug" "github.com/micro/go-micro/transport" "google.golang.org/grpc" "google.golang.org/grpc/encoding" @@ -89,10 +88,6 @@ func newOptions(opt ...server.Option) server.Options { opts.Transport = transport.DefaultTransport } - if opts.DebugHandler == nil { - opts.DebugHandler = debug.DefaultDebugHandler - } - if len(opts.Address) == 0 { opts.Address = server.DefaultAddress } diff --git a/server/grpc/request.go b/server/grpc/request.go index 951c1a19..617b9a7d 100644 --- a/server/grpc/request.go +++ b/server/grpc/request.go @@ -2,6 +2,7 @@ package grpc import ( "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/bytes" ) type rpcRequest struct { @@ -46,7 +47,11 @@ func (r *rpcRequest) Header() map[string]string { } func (r *rpcRequest) Read() ([]byte, error) { - return r.body, nil + f := &bytes.Frame{} + if err := r.codec.ReadBody(f); err != nil { + return nil, err + } + return f.Data, nil } func (r *rpcRequest) Stream() bool { diff --git a/server/grpc/response.go b/server/grpc/response.go index 451b1f4e..f13ad89c 100644 --- a/server/grpc/response.go +++ b/server/grpc/response.go @@ -1,15 +1,11 @@ package grpc import ( - "net/http" - "github.com/micro/go-micro/codec" - "github.com/micro/go-micro/transport" ) type rpcResponse struct { header map[string]string - socket transport.Socket codec codec.Codec } @@ -24,12 +20,8 @@ func (r *rpcResponse) WriteHeader(hdr map[string]string) { } func (r *rpcResponse) Write(b []byte) error { - if _, ok := r.header["Content-Type"]; !ok { - r.header["Content-Type"] = http.DetectContentType(b) - } - - return r.socket.Send(&transport.Message{ + return r.codec.Write(&codec.Message{ Header: r.header, Body: b, - }) + }, nil) } diff --git a/server/grpc/subscriber.go b/server/grpc/subscriber.go index 7a9f19af..3bd4abef 100644 --- a/server/grpc/subscriber.go +++ b/server/grpc/subscriber.go @@ -12,6 +12,7 @@ import ( "github.com/micro/go-micro/metadata" "github.com/micro/go-micro/registry" "github.com/micro/go-micro/server" + "github.com/micro/go-micro/util/buf" ) const ( @@ -167,7 +168,7 @@ func validateSubscriber(sub server.Subscriber) error { } func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broker.Handler { - return func(p broker.Publication) error { + return func(p broker.Event) error { msg := p.Message() ct := msg.Header["Content-Type"] if len(ct) == 0 { @@ -204,11 +205,11 @@ func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broke req = req.Elem() } - b := &buffer{bytes.NewBuffer(msg.Body)} + b := buf.New(bytes.NewBuffer(msg.Body)) co := cf(b) defer co.Close() - if err := co.ReadHeader(&codec.Message{}, codec.Publication); err != nil { + if err := co.ReadHeader(&codec.Message{}, codec.Event); err != nil { return err } diff --git a/server/options.go b/server/options.go index 15de9b83..c75ed4d1 100644 --- a/server/options.go +++ b/server/options.go @@ -8,7 +8,6 @@ import ( "github.com/micro/go-micro/broker" "github.com/micro/go-micro/codec" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/server/debug" "github.com/micro/go-micro/transport" ) @@ -36,9 +35,6 @@ type Options struct { // The router for requests Router Router - // Debug Handler which can be set by a user - DebugHandler debug.DebugHandler - // Other options for implementations of the interface // can be stored in a context Context context.Context @@ -66,10 +62,6 @@ func newOptions(opt ...Option) Options { opts.Transport = transport.DefaultTransport } - if opts.DebugHandler == nil { - opts.DebugHandler = debug.DefaultDebugHandler - } - if opts.RegisterCheck == nil { opts.RegisterCheck = DefaultRegisterCheck } @@ -156,13 +148,6 @@ func Transport(t transport.Transport) Option { } } -// DebugHandler for this server -func DebugHandler(d debug.DebugHandler) Option { - return func(o *Options) { - o.DebugHandler = d - } -} - // Metadata associated with the server func Metadata(md map[string]string) Option { return func(o *Options) { diff --git a/server/rpc_router.go b/server/rpc_router.go index 837137ae..b1eb66ff 100644 --- a/server/rpc_router.go +++ b/server/rpc_router.go @@ -188,7 +188,11 @@ func (s *service) call(ctx context.Context, router *router, sending *sync.Mutex, method: req.msg.Method, endpoint: req.msg.Endpoint, body: req.msg.Body, - rawBody: argv.Interface(), + } + + // only set if not nil + if argv.IsValid() { + r.rawBody = argv.Interface() } if !mtype.stream { diff --git a/server/rpc_server.go b/server/rpc_server.go index 6caca4e3..b68e6425 100644 --- a/server/rpc_server.go +++ b/server/rpc_server.go @@ -3,6 +3,7 @@ package server import ( "context" "fmt" + "net" "runtime/debug" "sort" "strconv" @@ -17,6 +18,7 @@ import ( "github.com/micro/go-micro/transport" "github.com/micro/go-micro/util/addr" log "github.com/micro/go-micro/util/log" + mnet "github.com/micro/go-micro/util/net" ) type rpcServer struct { @@ -277,10 +279,11 @@ func (s *rpcServer) Subscribe(sb Subscriber) error { } func (s *rpcServer) Register() error { + var err error + var advt, host, port string + // parse address for host, port config := s.Options() - var advt, host string - var port int // check the advertise address first // if it exists then use it, otherwise @@ -291,12 +294,14 @@ func (s *rpcServer) Register() error { advt = config.Address } - parts := strings.Split(advt, ":") - if len(parts) > 1 { - host = strings.Join(parts[:len(parts)-1], ":") - port, _ = strconv.Atoi(parts[len(parts)-1]) + if cnt := strings.Count(advt, ":"); cnt >= 1 { + // ipv6 address in format [host]:port or ipv4 host:port + host, port, err = net.SplitHostPort(advt) + if err != nil { + return err + } } else { - host = parts[0] + host = advt } addr, err := addr.Extract(host) @@ -313,8 +318,7 @@ func (s *rpcServer) Register() error { // register service node := ®istry.Node{ Id: config.Name + "-" + config.Id, - Address: addr, - Port: port, + Address: mnet.HostPort(addr, port), Metadata: md, } @@ -406,6 +410,7 @@ func (s *rpcServer) Register() error { if err != nil { return err } + log.Logf("Subscribing %s to topic: %s", node.Id, sub.Topic()) s.subscribers[sb] = []broker.Subscriber{sub} } @@ -413,9 +418,10 @@ func (s *rpcServer) Register() error { } func (s *rpcServer) Deregister() error { + var err error + var advt, host, port string + config := s.Options() - var advt, host string - var port int // check the advertise address first // if it exists then use it, otherwise @@ -426,12 +432,14 @@ func (s *rpcServer) Deregister() error { advt = config.Address } - parts := strings.Split(advt, ":") - if len(parts) > 1 { - host = strings.Join(parts[:len(parts)-1], ":") - port, _ = strconv.Atoi(parts[len(parts)-1]) + if cnt := strings.Count(advt, ":"); cnt >= 1 { + // ipv6 address in format [host]:port or ipv4 host:port + host, port, err = net.SplitHostPort(advt) + if err != nil { + return err + } } else { - host = parts[0] + host = advt } addr, err := addr.Extract(host) @@ -441,8 +449,7 @@ func (s *rpcServer) Deregister() error { node := ®istry.Node{ Id: config.Name + "-" + config.Id, - Address: addr, - Port: port, + Address: mnet.HostPort(addr, port), } service := ®istry.Service{ @@ -467,7 +474,7 @@ func (s *rpcServer) Deregister() error { for sb, subs := range s.subscribers { for _, sub := range subs { - log.Logf("Unsubscribing from topic: %s", sub.Topic()) + log.Logf("Unsubscribing %s from topic: %s", node.Id, sub.Topic()) sub.Unsubscribe() } s.subscribers[sb] = nil @@ -478,7 +485,6 @@ func (s *rpcServer) Deregister() error { } func (s *rpcServer) Start() error { - registerDebugHandler(s) config := s.Options() // start listening on the transport @@ -500,7 +506,9 @@ func (s *rpcServer) Start() error { return err } - log.Logf("Broker [%s] Connected to %s", config.Broker.String(), config.Broker.Address()) + bname := config.Broker.String() + + log.Logf("Broker [%s] Connected to %s", bname, config.Broker.Address()) // use RegisterCheck func before register if err = s.opts.RegisterCheck(s.opts.Context); err != nil { diff --git a/server/server.go b/server/server.go index 2e742f95..b13048a3 100644 --- a/server/server.go +++ b/server/server.go @@ -142,6 +142,11 @@ func NewServer(opt ...Option) Server { return newRpcServer(opt...) } +// NewRouter returns a new router +func NewRouter() *router { + return newRpcRouter() +} + // NewSubscriber creates a new subscriber interface with the given topic // and handler using the default server func NewSubscriber(topic string, h interface{}, opts ...SubscriberOption) Subscriber { diff --git a/server/subscriber.go b/server/subscriber.go index 77971565..e3f29286 100644 --- a/server/subscriber.go +++ b/server/subscriber.go @@ -11,6 +11,7 @@ import ( "github.com/micro/go-micro/codec" "github.com/micro/go-micro/metadata" "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/util/buf" ) const ( @@ -165,7 +166,7 @@ func validateSubscriber(sub Subscriber) error { } func (s *rpcServer) createSubHandler(sb *subscriber, opts Options) broker.Handler { - return func(p broker.Publication) error { + return func(p broker.Event) error { msg := p.Message() // get codec @@ -210,11 +211,11 @@ func (s *rpcServer) createSubHandler(sb *subscriber, opts Options) broker.Handle req = req.Elem() } - b := &buffer{bytes.NewBuffer(msg.Body)} + b := buf.New(bytes.NewBuffer(msg.Body)) co := cf(b) defer co.Close() - if err := co.ReadHeader(&codec.Message{}, codec.Publication); err != nil { + if err := co.ReadHeader(&codec.Message{}, codec.Event); err != nil { return err } diff --git a/service.go b/service.go index 6b7c6508..e612a4b3 100644 --- a/service.go +++ b/service.go @@ -7,7 +7,8 @@ import ( "syscall" "github.com/micro/go-micro/client" - "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/config/cmd" + "github.com/micro/go-micro/debug/handler" "github.com/micro/go-micro/metadata" "github.com/micro/go-micro/server" ) @@ -113,6 +114,14 @@ func (s *service) Stop() error { } func (s *service) Run() error { + // register the debug handler + s.opts.Server.Handle( + s.opts.Server.NewHandler( + handler.DefaultHandler, + server.InternalHandler(true), + ), + ) + if err := s.Start(); err != nil { return err } diff --git a/service/options.go b/service/options.go new file mode 100644 index 00000000..9a077629 --- /dev/null +++ b/service/options.go @@ -0,0 +1,220 @@ +package service + +import ( + "context" + "time" + + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/transport" +) + +type Options struct { + Broker broker.Broker + Client client.Client + Server server.Server + Registry registry.Registry + Transport transport.Transport + + // Before and After funcs + BeforeStart []func() error + BeforeStop []func() error + AfterStart []func() error + AfterStop []func() error + + // Other options for implementations of the interface + // can be stored in a context + Context context.Context +} + +type Option func(*Options) + +func newOptions(opts ...Option) Options { + opt := Options{ + Broker: broker.DefaultBroker, + Client: client.DefaultClient, + Server: server.DefaultServer, + Registry: registry.DefaultRegistry, + Transport: transport.DefaultTransport, + Context: context.Background(), + } + + for _, o := range opts { + o(&opt) + } + + return opt +} + +func Broker(b broker.Broker) Option { + return func(o *Options) { + o.Broker = b + // Update Client and Server + o.Client.Init(client.Broker(b)) + o.Server.Init(server.Broker(b)) + } +} + +func Client(c client.Client) Option { + return func(o *Options) { + o.Client = c + } +} + +// Context specifies a context for the service. +// Can be used to signal shutdown of the service. +// Can be used for extra option values. +func Context(ctx context.Context) Option { + return func(o *Options) { + o.Context = ctx + } +} + +func Server(s server.Server) Option { + return func(o *Options) { + o.Server = s + } +} + +// Registry sets the registry for the service +// and the underlying components +func Registry(r registry.Registry) Option { + return func(o *Options) { + o.Registry = r + // Update Client and Server + o.Client.Init(client.Registry(r)) + o.Server.Init(server.Registry(r)) + // Update Broker + o.Broker.Init(broker.Registry(r)) + } +} + +// Transport sets the transport for the service +// and the underlying components +func Transport(t transport.Transport) Option { + return func(o *Options) { + o.Transport = t + // Update Client and Server + o.Client.Init(client.Transport(t)) + o.Server.Init(server.Transport(t)) + } +} + +// Convenience options + +// Address sets the address of the server +func Address(addr string) Option { + return func(o *Options) { + o.Server.Init(server.Address(addr)) + } +} + +// Name of the service +func Name(n string) Option { + return func(o *Options) { + o.Server.Init(server.Name(n)) + } +} + +// Version of the service +func Version(v string) Option { + return func(o *Options) { + o.Server.Init(server.Version(v)) + } +} + +// Metadata associated with the service +func Metadata(md map[string]string) Option { + return func(o *Options) { + o.Server.Init(server.Metadata(md)) + } +} + +// RegisterTTL specifies the TTL to use when registering the service +func RegisterTTL(t time.Duration) Option { + return func(o *Options) { + o.Server.Init(server.RegisterTTL(t)) + } +} + +// RegisterInterval specifies the interval on which to re-register +func RegisterInterval(t time.Duration) Option { + return func(o *Options) { + o.Server.Init(server.RegisterInterval(t)) + } +} + +// WrapClient is a convenience method for wrapping a Client with +// some middleware component. A list of wrappers can be provided. +// Wrappers are applied in reverse order so the last is executed first. +func WrapClient(w ...client.Wrapper) Option { + return func(o *Options) { + // apply in reverse + for i := len(w); i > 0; i-- { + o.Client = w[i-1](o.Client) + } + } +} + +// WrapCall is a convenience method for wrapping a Client CallFunc +func WrapCall(w ...client.CallWrapper) Option { + return func(o *Options) { + o.Client.Init(client.WrapCall(w...)) + } +} + +// WrapHandler adds a handler Wrapper to a list of options passed into the server +func WrapHandler(w ...server.HandlerWrapper) Option { + return func(o *Options) { + var wrappers []server.Option + + for _, wrap := range w { + wrappers = append(wrappers, server.WrapHandler(wrap)) + } + + // Init once + o.Server.Init(wrappers...) + } +} + +// WrapSubscriber adds a subscriber Wrapper to a list of options passed into the server +func WrapSubscriber(w ...server.SubscriberWrapper) Option { + return func(o *Options) { + var wrappers []server.Option + + for _, wrap := range w { + wrappers = append(wrappers, server.WrapSubscriber(wrap)) + } + + // Init once + o.Server.Init(wrappers...) + } +} + +// Before and Afters + +func BeforeStart(fn func() error) Option { + return func(o *Options) { + o.BeforeStart = append(o.BeforeStart, fn) + } +} + +func BeforeStop(fn func() error) Option { + return func(o *Options) { + o.BeforeStop = append(o.BeforeStop, fn) + } +} + +func AfterStart(fn func() error) Option { + return func(o *Options) { + o.AfterStart = append(o.AfterStart, fn) + } +} + +func AfterStop(fn func() error) Option { + return func(o *Options) { + o.AfterStop = append(o.AfterStop, fn) + } +} diff --git a/service/service.go b/service/service.go index 4dc92031..5b9d3027 100644 --- a/service/service.go +++ b/service/service.go @@ -1,2 +1,16 @@ // Package service encapsulates the client, server and other interfaces to provide a complete micro service. package service + +import ( + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/server" +) + +type Service interface { + Init(...Option) + Options() Options + Client() client.Client + Server() server.Server + Run() error + String() string +} diff --git a/service_test.go b/service_test.go index 8d4c4316..82195aed 100644 --- a/service_test.go +++ b/service_test.go @@ -8,8 +8,8 @@ import ( glog "github.com/go-log/log" "github.com/micro/go-micro/client" + proto "github.com/micro/go-micro/debug/proto" "github.com/micro/go-micro/registry/memory" - proto "github.com/micro/go-micro/server/debug/proto" "github.com/micro/go-micro/util/log" ) diff --git a/sync/map.go b/sync/map.go index 2053c57d..5f7865e9 100644 --- a/sync/map.go +++ b/sync/map.go @@ -6,8 +6,8 @@ import ( "encoding/json" "fmt" - "github.com/micro/go-micro/store" - ckv "github.com/micro/go-micro/store/consul" + "github.com/micro/go-micro/data/store" + ckv "github.com/micro/go-micro/data/store/consul" lock "github.com/micro/go-micro/sync/lock/consul" ) diff --git a/sync/options.go b/sync/options.go index 0e5cf3d0..65f20e28 100644 --- a/sync/options.go +++ b/sync/options.go @@ -1,7 +1,7 @@ package sync import ( - "github.com/micro/go-micro/store" + "github.com/micro/go-micro/data/store" "github.com/micro/go-micro/sync/leader" "github.com/micro/go-micro/sync/lock" "github.com/micro/go-micro/sync/time" diff --git a/sync/sync.go b/sync/sync.go index 7b080b1d..27a6104a 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -2,7 +2,7 @@ package sync import ( - "github.com/micro/go-micro/store" + "github.com/micro/go-micro/data/store" "github.com/micro/go-micro/sync/leader" "github.com/micro/go-micro/sync/lock" "github.com/micro/go-micro/sync/task" diff --git a/sync/task/broker/broker.go b/sync/task/broker/broker.go index af0ee673..f643fb2d 100644 --- a/sync/task/broker/broker.go +++ b/sync/task/broker/broker.go @@ -47,7 +47,7 @@ func (t *Task) Run(c task.Command) error { errCh := make(chan error, t.Options.Pool) // subscribe for distributed work - workFn := func(p broker.Publication) error { + workFn := func(p broker.Event) error { msg := p.Message() // get command name @@ -110,7 +110,7 @@ func (t *Task) Run(c task.Command) error { } // subscribe to all status messages - subStatus, err := t.Broker.Subscribe(topic, func(p broker.Publication) error { + subStatus, err := t.Broker.Subscribe(topic, func(p broker.Event) error { msg := p.Message() // get command name diff --git a/transport/grpc/grpc_test.go b/transport/grpc/grpc_test.go index d4e82346..b1e46ba5 100644 --- a/transport/grpc/grpc_test.go +++ b/transport/grpc/grpc_test.go @@ -1,15 +1,17 @@ package grpc import ( - "strings" + "net" "testing" "github.com/micro/go-micro/transport" ) func expectedPort(t *testing.T, expected string, lsn transport.Listener) { - parts := strings.Split(lsn.Addr(), ":") - port := parts[len(parts)-1] + _, port, err := net.SplitHostPort(lsn.Addr()) + if err != nil { + t.Errorf("Expected address to be `%s`, got error: %v", expected, err) + } if port != expected { lsn.Close() diff --git a/transport/http_transport.go b/transport/http_transport.go index ec098ca2..b1e760a4 100644 --- a/transport/http_transport.go +++ b/transport/http_transport.go @@ -14,16 +14,13 @@ import ( "time" maddr "github.com/micro/go-micro/util/addr" + "github.com/micro/go-micro/util/buf" mnet "github.com/micro/go-micro/util/net" mls "github.com/micro/go-micro/util/tls" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) -type buffer struct { - io.ReadWriter -} - type httpTransport struct { opts Options } @@ -65,10 +62,6 @@ type httpTransportListener struct { listener net.Listener } -func (b *buffer) Close() error { - return nil -} - func (h *httpTransportClient) Local() string { return h.local } @@ -84,11 +77,8 @@ func (h *httpTransportClient) Send(m *Message) error { header.Set(k, v) } - reqB := bytes.NewBuffer(m.Body) - defer reqB.Reset() - buf := &buffer{ - reqB, - } + b := buf.New(bytes.NewBuffer(m.Body)) + defer b.Close() req := &http.Request{ Method: "POST", @@ -97,8 +87,8 @@ func (h *httpTransportClient) Send(m *Message) error { Host: h.addr, }, Header: header, - Body: buf, - ContentLength: int64(reqB.Len()), + Body: b, + ContentLength: int64(b.Len()), Host: h.addr, } diff --git a/transport/http_transport_test.go b/transport/http_transport_test.go index fac80716..cbbc8658 100644 --- a/transport/http_transport_test.go +++ b/transport/http_transport_test.go @@ -2,14 +2,16 @@ package transport import ( "io" - "strings" + "net" "testing" "time" ) func expectedPort(t *testing.T, expected string, lsn Listener) { - parts := strings.Split(lsn.Addr(), ":") - port := parts[len(parts)-1] + _, port, err := net.SplitHostPort(lsn.Addr()) + if err != nil { + t.Errorf("Expected address to be `%s`, got error: %v", expected, err) + } if port != expected { lsn.Close() diff --git a/transport/memory/memory.go b/transport/memory/memory.go index ed00ce69..ed269367 100644 --- a/transport/memory/memory.go +++ b/transport/memory/memory.go @@ -2,14 +2,17 @@ package memory import ( + "context" "errors" "fmt" "math/rand" - "strings" + "net" "sync" "time" "github.com/micro/go-micro/transport" + maddr "github.com/micro/go-micro/util/addr" + mnet "github.com/micro/go-micro/util/net" ) type memorySocket struct { @@ -22,6 +25,11 @@ type memorySocket struct { local string remote string + + // for send/recv transport.Timeout + timeout time.Duration + ctx context.Context + sync.RWMutex } type memoryClient struct { @@ -30,21 +38,35 @@ type memoryClient struct { } type memoryListener struct { - addr string - exit chan bool - conn chan *memorySocket - opts transport.ListenOptions + addr string + exit chan bool + conn chan *memorySocket + lopts transport.ListenOptions + topts transport.Options + sync.RWMutex + ctx context.Context } type memoryTransport struct { opts transport.Options - - sync.Mutex + sync.RWMutex listeners map[string]*memoryListener } func (ms *memorySocket) Recv(m *transport.Message) error { + ms.RLock() + defer ms.RUnlock() + + ctx := ms.ctx + if ms.timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ms.ctx, ms.timeout) + defer cancel() + } + select { + case <-ctx.Done(): + return ctx.Err() case <-ms.exit: return errors.New("connection closed") case <-ms.lexit: @@ -64,7 +86,19 @@ func (ms *memorySocket) Remote() string { } func (ms *memorySocket) Send(m *transport.Message) error { + ms.RLock() + defer ms.RUnlock() + + ctx := ms.ctx + if ms.timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ms.ctx, ms.timeout) + defer cancel() + } + select { + case <-ctx.Done(): + return ctx.Err() case <-ms.exit: return errors.New("connection closed") case <-ms.lexit: @@ -75,6 +109,8 @@ func (ms *memorySocket) Send(m *transport.Message) error { } func (ms *memorySocket) Close() error { + ms.Lock() + defer ms.Unlock() select { case <-ms.exit: return nil @@ -89,6 +125,8 @@ func (m *memoryListener) Addr() string { } func (m *memoryListener) Close() error { + m.Lock() + defer m.Unlock() select { case <-m.exit: return nil @@ -105,20 +143,22 @@ func (m *memoryListener) Accept(fn func(transport.Socket)) error { return nil case c := <-m.conn: go fn(&memorySocket{ - lexit: c.lexit, - exit: c.exit, - send: c.recv, - recv: c.send, - local: c.Remote(), - remote: c.Local(), + lexit: c.lexit, + exit: c.exit, + send: c.recv, + recv: c.send, + local: c.Remote(), + remote: c.Local(), + timeout: m.topts.Timeout, + ctx: m.topts.Context, }) } } } func (m *memoryTransport) Dial(addr string, opts ...transport.DialOption) (transport.Client, error) { - m.Lock() - defer m.Unlock() + m.RLock() + defer m.RUnlock() listener, ok := m.listeners[addr] if !ok { @@ -132,12 +172,14 @@ func (m *memoryTransport) Dial(addr string, opts ...transport.DialOption) (trans client := &memoryClient{ &memorySocket{ - send: make(chan *transport.Message), - recv: make(chan *transport.Message), - exit: make(chan bool), - lexit: listener.exit, - local: addr, - remote: addr, + send: make(chan *transport.Message), + recv: make(chan *transport.Message), + exit: make(chan bool), + lexit: listener.exit, + local: addr, + remote: addr, + timeout: m.opts.Timeout, + ctx: m.opts.Context, }, options, } @@ -161,25 +203,36 @@ func (m *memoryTransport) Listen(addr string, opts ...transport.ListenOption) (t o(&options) } - parts := strings.Split(addr, ":") + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + addr, err = maddr.Extract(host) + if err != nil { + return nil, err + } // if zero port then randomly assign one - if len(parts) > 1 && parts[len(parts)-1] == "0" { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - i := r.Intn(10000) - // set addr with port - addr = fmt.Sprintf("%s:%d", parts[:len(parts)-1], 10000+i) + if len(port) > 0 && port == "0" { + i := rand.Intn(20000) + port = fmt.Sprintf("%d", 10000+i) } + // set addr with port + addr = mnet.HostPort(addr, port) + if _, ok := m.listeners[addr]; ok { return nil, errors.New("already listening on " + addr) } listener := &memoryListener{ - opts: options, - addr: addr, - conn: make(chan *memorySocket), - exit: make(chan bool), + lopts: options, + topts: m.opts, + addr: addr, + conn: make(chan *memorySocket), + exit: make(chan bool), + ctx: m.opts.Context, } m.listeners[addr] = listener @@ -204,10 +257,17 @@ func (m *memoryTransport) String() string { func NewTransport(opts ...transport.Option) transport.Transport { var options transport.Options + + rand.Seed(time.Now().UnixNano()) + for _, o := range opts { o(&options) } + if options.Context == nil { + options.Context = context.Background() + } + return &memoryTransport{ opts: options, listeners: make(map[string]*memoryListener), diff --git a/transport/memory/memory_test.go b/transport/memory/memory_test.go index 72952e09..d779f53e 100644 --- a/transport/memory/memory_test.go +++ b/transport/memory/memory_test.go @@ -10,7 +10,7 @@ func TestMemoryTransport(t *testing.T) { tr := NewTransport() // bind / listen - l, err := tr.Listen("localhost:8080") + l, err := tr.Listen("127.0.0.1:8080") if err != nil { t.Fatalf("Unexpected error listening %v", err) } @@ -37,7 +37,7 @@ func TestMemoryTransport(t *testing.T) { }() // dial - c, err := tr.Dial("localhost:8080") + c, err := tr.Dial("127.0.0.1:8080") if err != nil { t.Fatalf("Unexpected error dialing %v", err) } diff --git a/transport/options.go b/transport/options.go index 5aaebe4b..bea0d227 100644 --- a/transport/options.go +++ b/transport/options.go @@ -9,9 +9,17 @@ import ( ) type Options struct { - Addrs []string - Codec codec.Marshaler - Secure bool + // Addrs is the list of intermediary addresses to connect to + Addrs []string + // Codec is the codec interface to use where headers are not supported + // by the transport and the entire payload must be encoded + Codec codec.Marshaler + // Secure tells the transport to secure the connection. + // In the case TLSConfig is not specified best effort self-signed + // certs should be used + Secure bool + // TLSConfig to secure the connection. The assumption is that this + // is mTLS keypair TLSConfig *tls.Config // Timeout sets the timeout for Send/Recv Timeout time.Duration @@ -21,7 +29,10 @@ type Options struct { } type DialOptions struct { - Stream bool + // Tells the transport this is a streaming connection with + // multiple calls to send/recv and that send may not even be called + Stream bool + // Timeout for dialing Timeout time.Duration // TODO: add tls options when dialling diff --git a/transport/quic/quic.go b/transport/quic/quic.go index 2622ec84..8a1e3f61 100644 --- a/transport/quic/quic.go +++ b/transport/quic/quic.go @@ -2,6 +2,7 @@ package quic import ( + "context" "crypto/tls" "encoding/gob" @@ -67,12 +68,12 @@ func (q *quicListener) Close() error { func (q *quicListener) Accept(fn func(transport.Socket)) error { for { - s, err := q.l.Accept() + s, err := q.l.Accept(context.TODO()) if err != nil { return err } - stream, err := s.AcceptStream() + stream, err := s.AcceptStream(context.TODO()) if err != nil { continue } @@ -109,6 +110,7 @@ func (q *quicTransport) Dial(addr string, opts ...transport.DialOption) (transpo if config == nil { config = &tls.Config{ InsecureSkipVerify: true, + NextProtos: []string{"http/1.1"}, } } s, err := quic.DialAddr(addr, config, nil) @@ -116,7 +118,7 @@ func (q *quicTransport) Dial(addr string, opts ...transport.DialOption) (transpo return nil, err } - st, err := s.OpenStreamSync() + st, err := s.OpenStreamSync(context.TODO()) if err != nil { return nil, err } @@ -150,6 +152,7 @@ func (q *quicTransport) Listen(addr string, opts ...transport.ListenOption) (tra } config = &tls.Config{ Certificates: []tls.Certificate{cfg}, + NextProtos: []string{"http/1.1"}, } } diff --git a/transport/transport.go b/transport/transport.go index 6e11e341..84f05a2b 100644 --- a/transport/transport.go +++ b/transport/transport.go @@ -1,10 +1,21 @@ -// Package transport is an interface for synchronous communication +// Package transport is an interface for synchronous connection based communication package transport import ( "time" ) +// Transport is an interface which is used for communication between +// services. It uses connection based socket send/recv semantics and +// has various implementations; http, grpc, quic. +type Transport interface { + Init(...Option) error + Options() Options + Dial(addr string, opts ...DialOption) (Client, error) + Listen(addr string, opts ...ListenOption) (Listener, error) + String() string +} + type Message struct { Header map[string]string Body []byte @@ -28,17 +39,6 @@ type Listener interface { Accept(func(Socket)) error } -// Transport is an interface which is used for communication between -// services. It uses socket send/recv semantics and had various -// implementations {HTTP, RabbitMQ, NATS, ...} -type Transport interface { - Init(...Option) error - Options() Options - Dial(addr string, opts ...DialOption) (Client, error) - Listen(addr string, opts ...ListenOption) (Listener, error) - String() string -} - type Option func(*Options) type DialOption func(*DialOptions) diff --git a/tunnel/default.go b/tunnel/default.go new file mode 100644 index 00000000..842d0a95 --- /dev/null +++ b/tunnel/default.go @@ -0,0 +1,449 @@ +package tunnel + +import ( + "crypto/sha256" + "errors" + "fmt" + "sync" + + "github.com/google/uuid" + "github.com/micro/go-micro/transport" + "github.com/micro/go-micro/util/log" +) + +// tun represents a network tunnel +type tun struct { + options Options + + sync.RWMutex + + // to indicate if we're connected or not + connected bool + + // the send channel for all messages + send chan *message + + // close channel + closed chan bool + + // a map of sockets based on Micro-Tunnel-Id + sockets map[string]*socket + + // outbound links + links map[string]*link + + // listener + listener transport.Listener +} + +type link struct { + transport.Socket + id string +} + +// create new tunnel on top of a link +func newTunnel(opts ...Option) *tun { + options := DefaultOptions() + for _, o := range opts { + o(&options) + } + + return &tun{ + options: options, + send: make(chan *message, 128), + closed: make(chan bool), + sockets: make(map[string]*socket), + links: make(map[string]*link), + } +} + +// getSocket returns a socket from the internal socket map. +// It does this based on the Micro-Tunnel-Id and Micro-Tunnel-Session +func (t *tun) getSocket(id, session string) (*socket, bool) { + // get the socket + t.RLock() + s, ok := t.sockets[id+session] + t.RUnlock() + return s, ok +} + +// newSocket creates a new socket and saves it +func (t *tun) newSocket(id, session string) (*socket, bool) { + // hash the id + h := sha256.New() + h.Write([]byte(id)) + id = fmt.Sprintf("%x", h.Sum(nil)) + + // new socket + s := &socket{ + id: id, + session: session, + closed: make(chan bool), + recv: make(chan *message, 128), + send: t.send, + wait: make(chan bool), + } + + // save socket + t.Lock() + _, ok := t.sockets[id+session] + if ok { + // socket already exists + t.Unlock() + return nil, false + } + t.sockets[id+session] = s + t.Unlock() + + // return socket + return s, true +} + +// TODO: use tunnel id as part of the session +func (t *tun) newSession() string { + return uuid.New().String() +} + +// process outgoing messages sent by all local sockets +func (t *tun) process() { + // manage the send buffer + // all pseudo sockets throw everything down this + for { + select { + case msg := <-t.send: + nmsg := &transport.Message{ + Header: msg.data.Header, + Body: msg.data.Body, + } + + if nmsg.Header == nil { + nmsg.Header = make(map[string]string) + } + + // set the tunnel id on the outgoing message + nmsg.Header["Micro-Tunnel-Id"] = msg.id + + // set the session id + nmsg.Header["Micro-Tunnel-Session"] = msg.session + + // send the message via the interface + t.RLock() + for _, link := range t.links { + link.Send(nmsg) + } + t.RUnlock() + case <-t.closed: + return + } + } +} + +// process incoming messages +func (t *tun) listen(link transport.Socket, listener bool) { + for { + // process anything via the net interface + msg := new(transport.Message) + err := link.Recv(msg) + if err != nil { + return + } + + switch msg.Header["Micro-Tunnel"] { + case "connect", "close": + continue + } + + // the tunnel id + id := msg.Header["Micro-Tunnel-Id"] + + // the session id + session := msg.Header["Micro-Tunnel-Session"] + + // if the session id is blank there's nothing we can do + // TODO: check this is the case, is there any reason + // why we'd have a blank session? Is the tunnel + // used for some other purpose? + if len(id) == 0 || len(session) == 0 { + continue + } + + var s *socket + var exists bool + + // if its a local listener then we use that as the session id + // e.g we're using a loopback connecting to ourselves + if listener { + s, exists = t.getSocket(id, "listener") + } else { + // get the socket based on the tunnel id and session + // this could be something we dialed in which case + // we have a session for it otherwise its a listener + s, exists = t.getSocket(id, session) + if !exists { + // try get it based on just the tunnel id + // the assumption here is that a listener + // has no session but its set a listener session + s, exists = t.getSocket(id, "listener") + } + } + + // no socket in existence + if !exists { + // drop it, we don't care about + // messages we don't know about + continue + } + + // is the socket closed? + select { + case <-s.closed: + // closed + delete(t.sockets, id) + continue + default: + // process + } + + // is the socket new? + select { + // if its new the socket is actually blocked waiting + // for a connection. so we check if its waiting. + case <-s.wait: + // if its waiting e.g its new then we close it + default: + // set remote address of the socket + s.remote = msg.Header["Remote"] + close(s.wait) + } + + // construct a new transport message + tmsg := &transport.Message{ + Header: msg.Header, + Body: msg.Body, + } + + // construct the internal message + imsg := &message{ + id: id, + session: session, + data: tmsg, + } + + // append to recv backlog + // we don't block if we can't pass it on + select { + case s.recv <- imsg: + default: + } + } +} + +func (t *tun) connect() error { + l, err := t.options.Transport.Listen(t.options.Address) + if err != nil { + return err + } + + // save the listener + t.listener = l + + go func() { + // accept inbound connections + err := l.Accept(func(sock transport.Socket) { + log.Debugf("Tunnel accepted connection from %s", sock.Remote()) + // save the link + id := uuid.New().String() + t.Lock() + t.links[id] = &link{ + Socket: sock, + id: id, + } + t.Unlock() + + // delete the link + defer func() { + t.Lock() + delete(t.links, id) + t.Unlock() + }() + + // listen for inbound messages + t.listen(sock, true) + }) + + t.Lock() + defer t.Unlock() + + // still connected but the tunnel died + if err != nil && t.connected { + log.Logf("Tunnel listener died: %v", err) + } + }() + + for _, node := range t.options.Nodes { + // skip zero length nodes + if len(node) == 0 { + continue + } + + log.Debugf("Tunnel dialing %s", node) + c, err := t.options.Transport.Dial(node) + if err != nil { + log.Debugf("Tunnel failed to connect to %s: %v", node, err) + continue + } + log.Debugf("Tunnel connected to %s", node) + + if err := c.Send(&transport.Message{ + Header: map[string]string{ + "Micro-Tunnel": "connect", + }, + }); err != nil { + continue + } + + // process incoming messages + go t.listen(c, false) + + // save the link + id := uuid.New().String() + t.links[id] = &link{ + Socket: c, + id: id, + } + } + + // process outbound messages to be sent + // process sends to all links + go t.process() + + return nil +} + +func (t *tun) close() error { + // close all the links + for id, link := range t.links { + link.Send(&transport.Message{ + Header: map[string]string{ + "Micro-Tunnel": "close", + }, + }) + link.Close() + delete(t.links, id) + } + + // close the listener + return t.listener.Close() +} + +// Close the tunnel +func (t *tun) Close() error { + t.Lock() + defer t.Unlock() + + if !t.connected { + return nil + } + + select { + case <-t.closed: + return nil + default: + // close all the sockets + for _, s := range t.sockets { + s.Close() + } + // close the connection + close(t.closed) + t.connected = false + + // send a close message + // we don't close the link + // just the tunnel + return t.close() + } + + return nil +} + +// Connect the tunnel +func (t *tun) Connect() error { + t.Lock() + defer t.Unlock() + + // already connected + if t.connected { + return nil + } + + // send the connect message + if err := t.connect(); err != nil { + return err + } + + // set as connected + t.connected = true + // create new close channel + t.closed = make(chan bool) + + return nil +} + +func (t *tun) Init(opts ...Option) error { + for _, o := range opts { + o(&t.options) + } + return nil +} + +// Dial an address +func (t *tun) Dial(addr string) (Conn, error) { + c, ok := t.newSocket(addr, t.newSession()) + if !ok { + return nil, errors.New("error dialing " + addr) + } + + // set remote + c.remote = addr + // set local + c.local = "local" + + return c, nil +} + +// Accept a connection on the address +func (t *tun) Listen(addr string) (Listener, error) { + // create a new socket by hashing the address + c, ok := t.newSocket(addr, "listener") + if !ok { + return nil, errors.New("already listening on " + addr) + } + + // set remote. it will be replaced by the first message received + c.remote = "remote" + // set local + c.local = addr + + tl := &tunListener{ + addr: addr, + // the accept channel + accept: make(chan *socket, 128), + // the channel to close + closed: make(chan bool), + // tunnel closed channel + tunClosed: t.closed, + // the connection + conn: c, + // the listener socket + socket: c, + } + + // this kicks off the internal message processor + // for the listener so it can create pseudo sockets + // per session if they do not exist or pass messages + // to the existign sessions + go tl.process() + + // return the listener + return tl, nil +} diff --git a/tunnel/listener.go b/tunnel/listener.go new file mode 100644 index 00000000..070b313b --- /dev/null +++ b/tunnel/listener.go @@ -0,0 +1,108 @@ +package tunnel + +import ( + "io" +) + +type tunListener struct { + // address of the listener + addr string + // the accept channel + accept chan *socket + // the channel to close + closed chan bool + // the tunnel closed channel + tunClosed chan bool + // the connection + conn Conn + // the listener socket + socket *socket +} + +func (t *tunListener) process() { + // our connection map for session + conns := make(map[string]*socket) + + for { + select { + case <-t.closed: + return + // receive a new message + case m := <-t.socket.recv: + // get a socket + sock, ok := conns[m.session] + if !ok { + // create a new socket session + sock = &socket{ + // our tunnel id + id: m.id, + // the session id + session: m.session, + // close chan + closed: make(chan bool), + // recv called by the acceptor + recv: make(chan *message, 128), + // use the internal send buffer + send: t.socket.send, + // wait + wait: make(chan bool), + } + + // first message + sock.recv <- m + + // save the socket + conns[m.session] = sock + + // send to accept chan + select { + case <-t.closed: + return + case t.accept <- sock: + } + } + + // send this to the accept chan + select { + case <-sock.closed: + delete(conns, m.session) + case sock.recv <- m: + } + } + } +} + +func (t *tunListener) Addr() string { + return t.addr +} + +// Close closes tunnel listener +func (t *tunListener) Close() error { + select { + case <-t.closed: + return nil + default: + close(t.closed) + } + return nil +} + +// Everytime accept is called we essentially block till we get a new connection +func (t *tunListener) Accept() (Conn, error) { + select { + // if the socket is closed return + case <-t.closed: + return nil, io.EOF + case <-t.tunClosed: + // close the listener when the tunnel closes + close(t.closed) + return nil, io.EOF + // wait for a new connection + case c, ok := <-t.accept: + if !ok { + return nil, io.EOF + } + return c, nil + } + return nil, nil +} diff --git a/tunnel/options.go b/tunnel/options.go new file mode 100644 index 00000000..f152c8ac --- /dev/null +++ b/tunnel/options.go @@ -0,0 +1,63 @@ +package tunnel + +import ( + "github.com/google/uuid" + "github.com/micro/go-micro/transport" + "github.com/micro/go-micro/transport/quic" +) + +var ( + // DefaultAddress is default tunnel bind address + DefaultAddress = ":9096" +) + +type Option func(*Options) + +// Options provides network configuration options +type Options struct { + // Id is tunnel id + Id string + // Address is tunnel address + Address string + // Nodes are remote nodes + Nodes []string + // Transport listens to incoming connections + Transport transport.Transport +} + +// The tunnel id +func Id(id string) Option { + return func(o *Options) { + o.Id = id + } +} + +// The tunnel address +func Address(a string) Option { + return func(o *Options) { + o.Address = a + } +} + +// Nodes specify remote network nodes +func Nodes(n ...string) Option { + return func(o *Options) { + o.Nodes = n + } +} + +// Transport listens for incoming connections +func Transport(t transport.Transport) Option { + return func(o *Options) { + o.Transport = t + } +} + +// DefaultOptions returns router default options +func DefaultOptions() Options { + return Options{ + Id: uuid.New().String(), + Address: DefaultAddress, + Transport: quic.NewTransport(), + } +} diff --git a/tunnel/socket.go b/tunnel/socket.go new file mode 100644 index 00000000..ac738efc --- /dev/null +++ b/tunnel/socket.go @@ -0,0 +1,91 @@ +package tunnel + +import ( + "errors" + + "github.com/micro/go-micro/transport" +) + +// socket is our pseudo socket for transport.Socket +type socket struct { + // socket id based on Micro-Tunnel + id string + // the session id based on Micro.Tunnel-Session + session string + // closed + closed chan bool + // remote addr + remote string + // local addr + local string + // send chan + send chan *message + // recv chan + recv chan *message + // wait until we have a connection + wait chan bool +} + +// message is sent over the send channel +type message struct { + // tunnel id + id string + // the session id + session string + // transport data + data *transport.Message +} + +func (s *socket) Remote() string { + return s.remote +} + +func (s *socket) Local() string { + return s.local +} + +func (s *socket) Id() string { + return s.id +} + +func (s *socket) Session() string { + return s.session +} + +func (s *socket) Send(m *transport.Message) error { + select { + case <-s.closed: + return errors.New("socket is closed") + default: + // no op + } + // append to backlog + s.send <- &message{id: s.id, session: s.session, data: m} + return nil +} + +func (s *socket) Recv(m *transport.Message) error { + select { + case <-s.closed: + return errors.New("socket is closed") + default: + // no op + } + // recv from backlog + msg := <-s.recv + // set message + *m = *msg.data + // return nil + return nil +} + +// Close closes the socket +func (s *socket) Close() error { + select { + case <-s.closed: + // no op + default: + close(s.closed) + } + return nil +} diff --git a/tunnel/transport/listener.go b/tunnel/transport/listener.go new file mode 100644 index 00000000..b7a7280c --- /dev/null +++ b/tunnel/transport/listener.go @@ -0,0 +1,30 @@ +package transport + +import ( + "github.com/micro/go-micro/transport" + "github.com/micro/go-micro/tunnel" +) + +type tunListener struct { + l tunnel.Listener +} + +func (t *tunListener) Addr() string { + return t.l.Addr() +} + +func (t *tunListener) Close() error { + return t.l.Close() +} + +func (t *tunListener) Accept(fn func(socket transport.Socket)) error { + for { + // accept connection + c, err := t.l.Accept() + if err != nil { + return err + } + // execute the function + go fn(c) + } +} diff --git a/tunnel/transport/transport.go b/tunnel/transport/transport.go new file mode 100644 index 00000000..d37468d2 --- /dev/null +++ b/tunnel/transport/transport.go @@ -0,0 +1,113 @@ +// Package transport provides a tunnel transport +package transport + +import ( + "context" + + "github.com/micro/go-micro/transport" + "github.com/micro/go-micro/tunnel" +) + +type tunTransport struct { + options transport.Options + + tunnel tunnel.Tunnel +} + +type tunnelKey struct{} + +type transportKey struct{} + +func (t *tunTransport) Init(opts ...transport.Option) error { + for _, o := range opts { + o(&t.options) + } + + // close the existing tunnel + if t.tunnel != nil { + t.tunnel.Close() + } + + // get the tunnel + tun, ok := t.options.Context.Value(tunnelKey{}).(tunnel.Tunnel) + if !ok { + tun = tunnel.NewTunnel() + } + + // get the transport + tr, ok := t.options.Context.Value(transportKey{}).(transport.Transport) + if ok { + tun.Init(tunnel.Transport(tr)) + } + + // set the tunnel + t.tunnel = tun + + return nil +} + +func (t *tunTransport) Dial(addr string, opts ...transport.DialOption) (transport.Client, error) { + if err := t.tunnel.Connect(); err != nil { + return nil, err + } + + c, err := t.tunnel.Dial(addr) + if err != nil { + return nil, err + } + + return c, nil +} + +func (t *tunTransport) Listen(addr string, opts ...transport.ListenOption) (transport.Listener, error) { + if err := t.tunnel.Connect(); err != nil { + return nil, err + } + + l, err := t.tunnel.Listen(addr) + if err != nil { + return nil, err + } + + return &tunListener{l}, nil +} + +func (t *tunTransport) Options() transport.Options { + return t.options +} + +func (t *tunTransport) String() string { + return "tunnel" +} + +// NewTransport honours the initialiser used in +func NewTransport(opts ...transport.Option) transport.Transport { + t := &tunTransport{ + options: transport.Options{}, + } + + // initialise + t.Init(opts...) + + return t +} + +// WithTransport sets the internal tunnel +func WithTunnel(t tunnel.Tunnel) transport.Option { + return func(o *transport.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, tunnelKey{}, t) + } +} + +// WithTransport sets the internal transport +func WithTransport(t transport.Transport) transport.Option { + return func(o *transport.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, transportKey{}, t) + } +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go new file mode 100644 index 00000000..3c84c7eb --- /dev/null +++ b/tunnel/tunnel.go @@ -0,0 +1,44 @@ +// Package tunnel provides gre network tunnelling +package tunnel + +import ( + "github.com/micro/go-micro/transport" +) + +// Tunnel creates a gre network tunnel on top of a link. +// It establishes multiple streams using the Micro-Tunnel-Id header +// and Micro-Tunnel-Session header. The tunnel id is a hash of +// the address being requested. +type Tunnel interface { + Init(opts ...Option) error + // Connect connects the tunnel + Connect() error + // Close closes the tunnel + Close() error + // Dial an endpoint + Dial(addr string) (Conn, error) + // Accept connections + Listen(addr string) (Listener, error) +} + +// The listener provides similar constructs to the transport.Listener +type Listener interface { + Addr() string + Close() error + Accept() (Conn, error) +} + +// Conn is a connection dialed or accepted which includes the tunnel id and session +type Conn interface { + // Specifies the tunnel id + Id() string + // The session + Session() string + // a transport socket + transport.Socket +} + +// NewTunnel creates a new tunnel +func NewTunnel(opts ...Option) Tunnel { + return newTunnel(opts...) +} diff --git a/tunnel/tunnel_test.go b/tunnel/tunnel_test.go new file mode 100644 index 00000000..1580d4a9 --- /dev/null +++ b/tunnel/tunnel_test.go @@ -0,0 +1,122 @@ +package tunnel + +import ( + "sync" + "testing" + "time" + + "github.com/micro/go-micro/transport" +) + +// testAccept will accept connections on the transport, create a new link and tunnel on top +func testAccept(t *testing.T, tun Tunnel, wg *sync.WaitGroup) { + // listen on some virtual address + tl, err := tun.Listen("test-tunnel") + if err != nil { + t.Fatal(err) + } + + // accept a connection + c, err := tl.Accept() + if err != nil { + t.Fatal(err) + } + + // get a message + for { + m := new(transport.Message) + if err := c.Recv(m); err != nil { + t.Fatal(err) + } + wg.Done() + return + } +} + +// testSend will create a new link to an address and then a tunnel on top +func testSend(t *testing.T, tun Tunnel) { + // dial a new session + c, err := tun.Dial("test-tunnel") + if err != nil { + t.Fatal(err) + } + defer c.Close() + + m := transport.Message{ + Header: map[string]string{ + "test": "header", + }, + } + + if err := c.Send(&m); err != nil { + t.Fatal(err) + } +} + +func TestTunnel(t *testing.T) { + // create a new listener + tun := NewTunnel(Nodes("127.0.0.1:9096")) + err := tun.Connect() + if err != nil { + t.Fatal(err) + } + defer tun.Close() + + var wg sync.WaitGroup + + // start accepting connections + wg.Add(1) + go testAccept(t, tun, &wg) + + // send a message + testSend(t, tun) + + // wait until message is received + wg.Wait() +} + +func TestTwoTunnel(t *testing.T) { + // create a new tunnel client + tunA := NewTunnel( + Address("127.0.0.1:9096"), + Nodes("127.0.0.1:9097"), + ) + + // create a new tunnel server + tunB := NewTunnel( + Address("127.0.0.1:9097"), + ) + + // start tunB + err := tunB.Connect() + if err != nil { + t.Fatal(err) + } + defer tunB.Close() + + time.Sleep(time.Millisecond * 50) + + // start tunA + err = tunA.Connect() + if err != nil { + t.Fatal(err) + } + defer tunA.Close() + + time.Sleep(time.Millisecond * 50) + + var wg sync.WaitGroup + + // start accepting connections + // on tunnel A + wg.Add(1) + go testAccept(t, tunA, &wg) + + time.Sleep(time.Millisecond * 50) + + // dial and send via B + testSend(t, tunB) + + // wait until done + wg.Wait() +} diff --git a/util/addr/addr.go b/util/addr/addr.go index ab3acca1..b2874533 100644 --- a/util/addr/addr.go +++ b/util/addr/addr.go @@ -30,7 +30,7 @@ func isPrivateIP(ipAddr string) bool { // Extract returns a real ip func Extract(addr string) (string, error) { // if addr specified then its returned - if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]") { + if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]" && addr != "::") { return addr, nil } @@ -113,10 +113,13 @@ func IPs() []string { continue } - ip = ip.To4() - if ip == nil { - continue - } + // dont skip ipv6 addrs + /* + ip = ip.To4() + if ip == nil { + continue + } + */ ipAddrs = append(ipAddrs, ip.String()) } diff --git a/server/grpc/buffer.go b/util/buf/buf.go similarity index 52% rename from server/grpc/buffer.go rename to util/buf/buf.go index c43bb231..14f07cd2 100644 --- a/server/grpc/buffer.go +++ b/util/buf/buf.go @@ -1,4 +1,4 @@ -package grpc +package buf import ( "bytes" @@ -12,3 +12,10 @@ func (b *buffer) Close() error { b.Buffer.Reset() return nil } + +func New(b *bytes.Buffer) *buffer { + if b == nil { + b = bytes.NewBuffer(nil) + } + return &buffer{b} +} diff --git a/util/grpc/grpc.go b/util/grpc/grpc.go index b06a0673..59c5a969 100644 --- a/util/grpc/grpc.go +++ b/util/grpc/grpc.go @@ -38,3 +38,20 @@ func ServiceMethod(m string) (string, string, error) { return parts[0], parts[1], nil } + +// ServiceFromMethod returns the service +// /service.Foo/Bar => service +func ServiceFromMethod(m string) string { + if len(m) == 0 { + return m + } + if m[0] != '/' { + return m + } + parts := strings.Split(m, "/") + if len(parts) < 3 { + return m + } + parts = strings.Split(parts[1], ".") + return strings.Join(parts[:len(parts)-1], ".") +} diff --git a/util/http/http.go b/util/http/http.go index 06bff69f..47fc33f4 100644 --- a/util/http/http.go +++ b/util/http/http.go @@ -3,8 +3,8 @@ package http import ( "net/http" + "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/selector" ) func NewRoundTripper(opts ...Option) http.RoundTripper { diff --git a/util/http/http_test.go b/util/http/http_test.go index b7bfe370..60196038 100644 --- a/util/http/http_test.go +++ b/util/http/http_test.go @@ -4,7 +4,6 @@ import ( "io/ioutil" "net" "net/http" - "strconv" "testing" "github.com/micro/go-micro/registry" @@ -30,16 +29,12 @@ func TestRoundTripper(t *testing.T) { go http.Serve(l, nil) - host, p, _ := net.SplitHostPort(l.Addr().String()) - port, _ := strconv.Atoi(p) - m.Register(®istry.Service{ Name: "example.com", Nodes: []*registry.Node{ { Id: "1", - Address: host, - Port: port, + Address: l.Addr().String(), }, }, }) diff --git a/util/http/roundtripper.go b/util/http/roundtripper.go index f90ef34f..36c26b5c 100644 --- a/util/http/roundtripper.go +++ b/util/http/roundtripper.go @@ -2,10 +2,9 @@ package http import ( "errors" - "fmt" "net/http" - "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/client/selector" ) type roundTripper struct { @@ -28,7 +27,7 @@ func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { if err != nil { continue } - req.URL.Host = fmt.Sprintf("%s:%d", n.Address, n.Port) + req.URL.Host = n.Address w, err := r.rt.RoundTrip(req) if err != nil { continue diff --git a/util/io/io.go b/util/io/io.go new file mode 100644 index 00000000..133ba6b8 --- /dev/null +++ b/util/io/io.go @@ -0,0 +1,40 @@ +// Package io is for io management +package io + +import ( + "io" + + "github.com/micro/go-micro/transport" +) + +type rwc struct { + socket transport.Socket +} + +func (r *rwc) Read(p []byte) (n int, err error) { + m := new(transport.Message) + if err := r.socket.Recv(m); err != nil { + return 0, err + } + copy(p, m.Body) + return len(m.Body), nil +} + +func (r *rwc) Write(p []byte) (n int, err error) { + err = r.socket.Send(&transport.Message{ + Body: p, + }) + if err != nil { + return 0, err + } + return len(p), nil +} + +func (r *rwc) Close() error { + return r.socket.Close() +} + +// NewRWC returns a new ReadWriteCloser +func NewRWC(sock transport.Socket) io.ReadWriteCloser { + return &rwc{sock} +} diff --git a/util/log/log.go b/util/log/log.go index 86208c06..4374aa1b 100644 --- a/util/log/log.go +++ b/util/log/log.go @@ -8,11 +8,37 @@ import ( golog "github.com/go-log/log/log" ) +// level is a log level +type Level int + +const ( + trace Level = iota + debug + info + fatal +) + var ( // the local logger logger log.Logger = golog.New() + + // default log level is debug + level = info ) +func init() { + switch os.Getenv("MICRO_LOG_LEVEL") { + case "debug": + level = debug + case "info": + level = info + case "trace": + level = trace + case "fatal": + level = fatal + } +} + // Log makes use of github.com/go-log/log.Log func Log(v ...interface{}) { logger.Log(v...) @@ -23,15 +49,61 @@ func Logf(format string, v ...interface{}) { logger.Logf(format, v...) } +// WithLevel logs with the level specified +func WithLevel(l Level, v ...interface{}) { + if l < level { + return + } + Log(v...) +} + +// WithLevel logs with the level specified +func WithLevelf(l Level, format string, v ...interface{}) { + if l < level { + return + } + Logf(format, v...) +} + +// Trace provides trace level logging +func Trace(v ...interface{}) { + WithLevel(trace, v...) +} + +// Tracef provides trace level logging +func Tracef(format string, v ...interface{}) { + WithLevelf(trace, format, v...) +} + +// Debug provides debug level logging +func Debug(v ...interface{}) { + WithLevel(debug, v...) +} + +// Debugf provides debug level logging +func Debugf(format string, v ...interface{}) { + WithLevelf(debug, format, v...) +} + +// Info provides info level logging +func Info(v ...interface{}) { + WithLevel(info, v...) +} + +// Infof provides info level logging +func Infof(format string, v ...interface{}) { + WithLevelf(info, format, v...) +} + // Fatal logs with Log and then exits with os.Exit(1) func Fatal(v ...interface{}) { - Log(v...) + WithLevel(fatal, v...) os.Exit(1) } // Fatalf logs with Logf and then exits with os.Exit(1) func Fatalf(format string, v ...interface{}) { - Logf(format, v...) + WithLevelf(fatal, format, v...) os.Exit(1) } @@ -39,3 +111,18 @@ func Fatalf(format string, v ...interface{}) { func SetLogger(l log.Logger) { logger = l } + +// GetLogger returns the local logger +func GetLogger() log.Logger { + return logger +} + +// SetLevel sets the log level +func SetLevel(l Level) { + level = l +} + +// GetLevel returns the current level +func GetLevel() Level { + return level +} diff --git a/util/net/net.go b/util/net/net.go index b092068f..678e2568 100644 --- a/util/net/net.go +++ b/util/net/net.go @@ -8,46 +8,61 @@ import ( "strings" ) +// HostPort format addr and port suitable for dial +func HostPort(addr string, port interface{}) string { + host := addr + if strings.Count(addr, ":") > 0 { + host = fmt.Sprintf("[%s]", addr) + } + // TODO check for NATS case + if v, ok := port.(string); ok { + if v == "" { + return fmt.Sprintf("%s", host) + } + } + return fmt.Sprintf("%s:%v", host, port) +} + // Listen takes addr:portmin-portmax and binds to the first available port // Example: Listen("localhost:5000-6000", fn) func Listen(addr string, fn func(string) (net.Listener, error)) (net.Listener, error) { - // host:port || host:min-max - parts := strings.Split(addr, ":") - // - if len(parts) < 2 { + if strings.Count(addr, ":") == 1 && strings.Count(addr, "-") == 0 { return fn(addr) } + // host:port || host:min-max + host, ports, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + // try to extract port range - ports := strings.Split(parts[len(parts)-1], "-") + prange := strings.Split(ports, "-") // single port - if len(ports) < 2 { + if len(prange) < 2 { return fn(addr) } // we have a port range // extract min port - min, err := strconv.Atoi(ports[0]) + min, err := strconv.Atoi(prange[0]) if err != nil { return nil, errors.New("unable to extract port range") } // extract max port - max, err := strconv.Atoi(ports[1]) + max, err := strconv.Atoi(prange[1]) if err != nil { return nil, errors.New("unable to extract port range") } - // set host - host := parts[:len(parts)-1] - // range the ports for port := min; port <= max; port++ { // try bind to host:port - ln, err := fn(fmt.Sprintf("%s:%d", host, port)) + ln, err := fn(HostPort(host, port)) if err == nil { return ln, nil } diff --git a/util/net/net_test.go b/util/net/net_test.go index a9fca743..d8516d88 100644 --- a/util/net/net_test.go +++ b/util/net/net_test.go @@ -18,4 +18,9 @@ func TestListen(t *testing.T) { } defer l.Close() } + + // TODO nats case test + // natsAddr := "_INBOX.bID2CMRvlNp0vt4tgNBHWf" + // Expect addr DO NOT has extra ":" at the end! + } diff --git a/web/README.md b/web/README.md deleted file mode 100644 index 82084b96..00000000 --- a/web/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# Go Web - -**Go Web** is a framework for micro service web development. - -## Overview - -Go Web provides a tiny HTTP web server library which leverages [go-micro](https://github.com/micro/go-micro) to create -micro web services as first class citizens in a microservice world. It wraps go-micro to give you service discovery, -heartbeating and the ability to create web apps as microservices. - -## Features - -- **Service Discovery** - Services are automatically registered in service discovery on startup. Go Web includes -a http.Client with pre-initialised roundtripper which makes use of service discovery so you can use service names. - -- **Heartbeating** - Go Web apps will periodically heartbeat with service discovery to provide liveness updates. -In the event a service fails it will be removed from the registry after a pre-defined expiry time. - -- **Custom Handlers** - Specify your own http router for handling requests. This allows you to maintain full -control over how you want to route to internal handlers. - -- **Static Serving** - Go Web automatically detects a local static `html` dir and serves files if no route handler -is specified. A quick solution for those who want to write JS web apps as microservices. - -## Getting Started - -- [Dependencies](#dependencies) -- [Usage](#usage) -- [Set Handler](#set-handler) -- [Call Service](#call-service) -- [Static Files](#static-files) - -## Dependencies - -Go Web makes use of Go Micro which means it needs service discovery - -See the [go-micro](https://github.com/micro/go-micro#service-discovery) for install instructions - -For a quick start use consul - -``` -# install -brew install consul - -# run -consul agent -dev -``` - -## Usage - -```go -service := web.NewService( - web.Name("example.com"), -) - -service.HandleFunc("/foo", fooHandler) - -if err := service.Init(); err != nil { - log.Fatal(err) -} - -if err := service.Run(); err != nil { - log.Fatal(err) -} -``` - -## Set Handler - -You might have a preference for a HTTP handler, so use something else. This loses the ability to register endpoints in discovery -but we'll fix that soon. - -```go -import "github.com/gorilla/mux" - -r := mux.NewRouter() -r.HandleFunc("/", indexHandler) -r.HandleFunc("/objects/{object}", objectHandler) - -service := web.NewService( - web.Handler(r) -) -``` - -## Call Service - -Go-web includes a http.Client with a custom http.RoundTripper that uses service discovery - -```go -c := service.Client() - -rsp, err := c.Get("http://example.com/foo") -``` - -This will lookup service discovery for the service `example.com` and route to one of the available nodes. - -## Static Files - -Go web was always meant as a way to register web apps where the majority of the code would be written in JS. To enable that by default, if no handler is registered on "/" and we find a local "html" directory then static files will be served. - -You will see a log output like so. - -``` -2019/05/12 14:55:47 Enabling static file serving from /tmp/foo/html -``` - -If you want to set this path manually use the StaticDir option. If a relative path is specified we will use os.Getwd() and prefix this. - -``` -service := web.NewService( - web.Name("example.com"), - web.StaticDir("/tmp/example.com/html"), -) - -``` diff --git a/web/examples/README.md b/web/examples/README.md deleted file mode 100644 index 5208291c..00000000 --- a/web/examples/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Examples - -Name | Description ---- | --- -[Message](https://github.com/micro/message-web) | A simple text based messaging web app -[Geo](https://github.com/micro/geo-web) | A geo location map demo - diff --git a/web/examples/helloworld/helloworld.go b/web/examples/helloworld/helloworld.go deleted file mode 100644 index 017b3b8b..00000000 --- a/web/examples/helloworld/helloworld.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - - "github.com/micro/go-micro/web" -) - -func helloWorldHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `

Hello World

`) -} - -func main() { - service := web.NewService( - web.Name("helloworld"), - ) - - service.HandleFunc("/", helloWorldHandler) - - if err := service.Init(); err != nil { - log.Fatal(err) - } - - if err := service.Run(); err != nil { - log.Fatal(err) - } -} diff --git a/web/service.go b/web/service.go index e4836472..65359a2b 100644 --- a/web/service.go +++ b/web/service.go @@ -2,6 +2,7 @@ package web import ( "crypto/tls" + "fmt" "net" "net/http" "os" @@ -83,8 +84,7 @@ func (s *service) genSrv() *registry.Service { Version: s.opts.Version, Nodes: []*registry.Node{®istry.Node{ Id: s.opts.Id, - Address: addr, - Port: port, + Address: fmt.Sprintf("%s:%d", addr, port), Metadata: s.opts.Metadata, }}, } diff --git a/web/service_test.go b/web/service_test.go index f9d5af8d..588531b8 100644 --- a/web/service_test.go +++ b/web/service_test.go @@ -75,7 +75,7 @@ func TestService(t *testing.T) { t.Fatalf("Expected %d but got %d services", want, have) } - rsp, err := http.Get(fmt.Sprintf("http://%s:%d", s[0].Nodes[0].Address, s[0].Nodes[0].Port)) + rsp, err := http.Get(fmt.Sprintf("http://%s", s[0].Nodes[0].Address)) if err != nil { t.Fatal(err) } @@ -243,7 +243,7 @@ func TestTLS(t *testing.T) { TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := &http.Client{Transport: tr} - rsp, err := client.Get(fmt.Sprintf("https://%s:%d", s[0].Nodes[0].Address, s[0].Nodes[0].Port)) + rsp, err := client.Get(fmt.Sprintf("https://%s", s[0].Nodes[0].Address)) if err != nil { t.Fatal(err) }