Compare commits
332 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
852abcaaed | ||
|
11f80708ce | ||
|
104778e5e5 | ||
|
ae99b9a887 | ||
|
8fdc050e2e | ||
|
8855beb62d | ||
|
47acdf6a4b | ||
|
4fc9b9821a | ||
|
a5fb124b22 | ||
|
5b327ce723 | ||
|
2b5bf1154a | ||
|
b7b8f8bf11 | ||
|
a63dcda003 | ||
|
1db98ee0f0 | ||
|
f2669e7b1e | ||
|
adb6760e21 | ||
|
2b3a87a212 | ||
|
3d2ec5dbb1 | ||
|
96f9ce1bd3 | ||
|
cb3052ce04 | ||
|
2f1658c213 | ||
|
d8b00e801d | ||
|
002abca61f | ||
|
c5740ae031 | ||
|
ddad43bd77 | ||
|
b6fb969ab9 | ||
|
22d0f1f08f | ||
|
c3a8146d99 | ||
|
2338780a61 | ||
|
e22c4b4c07 | ||
|
100cb9db6b | ||
|
4e27aac398 | ||
|
7ca06f0c1d | ||
|
7ca8f8f0ab | ||
|
9ad5ae6644 | ||
|
220a8fafb1 | ||
|
809de7a052 | ||
|
23f0231a09 | ||
|
74cbce72df | ||
|
b55adc0c30 | ||
|
388ac34b7c | ||
|
13a8cfe7f3 | ||
|
1e94d9fe5a | ||
|
49dcc3d1bd | ||
|
481ebe9d4f | ||
|
502f6d3e9f | ||
|
8f2585724c | ||
1217ca94b1 | |||
|
96cf14ed53 | ||
|
3a8edd705c | ||
|
94b6455577 | ||
e688ab0a45 | |||
|
2803146673 | ||
|
d4fefc4b76 | ||
a3bddf5839 | |||
|
92495d22db | ||
|
8c7e35c3c6 | ||
|
c108188d65 | ||
|
609934ce99 | ||
|
aa79c41fc5 | ||
|
a549f92dec | ||
81d2259fac | |||
|
2fecde1dbb | ||
008749b2b0 | |||
|
3ccb900bca | ||
a72e1185da | |||
|
5157241c88 | ||
|
70d811c47a | ||
|
b371704444 | ||
|
a5f21e69ad | ||
|
6b984136f7 | ||
|
9c851f297b | ||
|
dac8a13a77 | ||
|
360e193a01 | ||
|
35a1de91a9 | ||
|
7631463b94 | ||
|
6581586226 | ||
|
06c29302d7 | ||
|
dab0e9e9bc | ||
|
47d91a1f64 | ||
|
bdeae91063 | ||
|
c8d57032bc | ||
|
3abe3aa28b | ||
|
9b1cb4ef0e | ||
|
b4796724d9 | ||
|
ae5376cc0e | ||
|
7bee0629c2 | ||
29fa8de98e | |||
|
382fbecd40 | ||
|
a0ee7d2092 | ||
|
1f744b31a4 | ||
|
998a23c963 | ||
|
e17ecf66b1 | ||
|
c5dd737568 | ||
|
7c29be288b | ||
|
217f540601 | ||
|
ffae0f0fab | ||
|
4cca2b43a3 | ||
|
8c157c1d5f | ||
|
1f218f7b48 | ||
|
7e0d4fe0cf | ||
|
0a39fe39c3 | ||
|
163b917ec7 | ||
|
0f16eb2858 | ||
|
89231f701b | ||
|
196e76e350 | ||
|
f3d9177233 | ||
|
8b7ac8a3f9 | ||
|
8f5aed707e | ||
|
c71576a538 | ||
|
27cfc06828 | ||
|
717ba4b3c0 | ||
|
4e3a230356 | ||
|
66c2519696 | ||
|
86dfa82dfa | ||
|
55f8045a70 | ||
|
b23d955536 | ||
|
5b565f9f10 | ||
|
9955ed2034 | ||
|
c36107e811 | ||
|
a08b64c8ab | ||
|
64ec0633a3 | ||
|
0a1b657221 | ||
|
34967e8e33 | ||
|
eda380284c | ||
|
0bf54c122f | ||
|
97282a5377 | ||
|
b642d5e1c0 | ||
|
c5a282ddd3 | ||
|
6cf8bde612 | ||
|
327029beff | ||
|
c5214c931f | ||
|
d725980444 | ||
|
23cb811f60 | ||
|
c5fb409760 | ||
|
70665e5a7d | ||
|
449aa0a339 | ||
|
265271008e | ||
|
b82245429e | ||
|
cc590f5f2c | ||
|
0c1a28a9b6 | ||
|
30d05e34a9 | ||
|
b68f0e237f | ||
|
72ef032162 | ||
|
d6c07dfb16 | ||
|
ea872f6900 | ||
|
6bdc23a3aa | ||
|
fa54db5ba5 | ||
|
8015a1daaf | ||
|
608a1f8add | ||
|
4a02e1ff2f | ||
|
5cd1e81ba9 | ||
|
d3edad474e | ||
|
e0bf1c2283 | ||
|
b655f7f55a | ||
|
5b7454e5a8 | ||
|
0b732b2c49 | ||
|
be33d9204a | ||
|
4b4ad68eb9 | ||
|
79b03a6825 | ||
|
777a203f96 | ||
|
c1097a4509 | ||
|
5f664faeba | ||
|
d2d6841f02 | ||
|
eafc930f84 | ||
|
d1fc3c361e | ||
e40307c567 | |||
|
a412486c39 | ||
|
59a0e727e4 | ||
|
b35f227f7a | ||
|
00ba1655ca | ||
|
e88041dc26 | ||
0e34c572b4 | |||
|
2644497ccb | ||
|
e54de56376 | ||
|
7008809eff | ||
|
f619e46def | ||
|
c3611aead2 | ||
|
686aa3aa05 | ||
|
543dc0166c | ||
|
372ad949ff | ||
|
a0c2d18c40 | ||
|
b4236f4430 | ||
|
0e1fcc4f28 | ||
|
8f22e61a8b | ||
|
956902f641 | ||
|
ffac0b9a18 | ||
|
c108b51d2a | ||
|
5fd798c9b6 | ||
|
ebe3633082 | ||
|
032c3134c6 | ||
|
8ccf61ebaf | ||
|
fbbc33d0f9 | ||
|
da299ea26b | ||
|
d3e200575c | ||
|
ddee8412ff | ||
|
c84f101e17 | ||
|
f6e064cdbd | ||
|
cff46c3fd8 | ||
|
32300eadc1 | ||
|
8ad2f73ad6 | ||
|
9d7420658d | ||
|
0971deb9cc | ||
|
0899282277 | ||
|
d8e998ad85 | ||
|
b4b76d452a | ||
|
67e3d560fe | ||
|
9630e153a5 | ||
|
43297f731c | ||
|
f6f6e1b561 | ||
|
4bee5c1b2b | ||
|
3b0ef425b6 | ||
|
5334203435 | ||
|
0da8256426 | ||
|
940ea94a96 | ||
|
b904f383c1 | ||
|
cedcef032d | ||
|
76011b151d | ||
|
27b145f968 | ||
|
ac098e4d78 | ||
|
1a62c11166 | ||
|
fe84a2d726 | ||
|
c282125f09 | ||
|
4cad7697cc | ||
|
a8dbca756c | ||
|
8e4fd16aff | ||
|
68764ebafc | ||
|
4d08618517 | ||
|
e5959f80d6 | ||
|
b89423bf37 | ||
|
9a56c4e0b2 | ||
|
4f982bb9cd | ||
|
1277f2478d | ||
|
dffbe045e4 | ||
|
c3d2043caf | ||
|
79cc8e34b0 | ||
|
2d91ba411e | ||
|
5ee7140aa3 | ||
|
6ef838c9aa | ||
|
1b4005e9a5 | ||
|
3f97743e34 | ||
|
7936d74602 | ||
|
6db720b197 | ||
|
ca5acba0c6 | ||
|
b4acb9bb58 | ||
|
c350e19552 | ||
|
4aa0192eba | ||
|
3c82b2e9e8 | ||
|
9514bd7b2a | ||
|
7acd249147 | ||
|
1983b4ae92 | ||
|
92b998c3ab | ||
|
1765be049b | ||
|
8d5d812e32 | ||
|
3f910038a3 | ||
|
a8042adac1 | ||
|
10a3636a9f | ||
|
4e5fbbf7eb | ||
|
59035ab801 | ||
|
d3525ebab3 | ||
|
2674294cbe | ||
|
b20dd16f92 | ||
|
5088c9d916 | ||
|
f62fcaad76 | ||
|
322eaae529 | ||
|
6a33b7576b | ||
|
6e669d4611 | ||
|
95fc625e99 | ||
|
338e0fdf18 | ||
|
5899134b66 | ||
|
da18ea4ab5 | ||
|
459f4c8387 | ||
|
9c57f32f58 | ||
|
ad92e6821e | ||
|
d7f0db04ec | ||
|
e4311c3a10 | ||
|
ee8b6b3114 | ||
|
08da7c1283 | ||
|
6587ae07be | ||
|
1c1dae0642 | ||
|
a0cb105cf6 | ||
|
606b1ff7cf | ||
|
73a8b14145 | ||
|
c0a628d65b | ||
|
e9c2df775a | ||
|
d3a6297b17 | ||
|
7266c62d09 | ||
|
6459cdfc21 | ||
|
ed54384bf4 | ||
|
51560009d2 | ||
|
cf2f8a9a55 | ||
|
97cf2cd7c3 | ||
|
d9fe8f802b | ||
|
b754c33549 | ||
|
59eaa89bac | ||
|
f65694670e | ||
|
1a571b8c82 | ||
|
308673b393 | ||
|
3a454d870a | ||
|
baaa386e27 | ||
|
a619321b64 | ||
|
363fb551af | ||
|
7a87ae0efa | ||
ab692ff590 | |||
|
2b18b11ab1 | ||
|
af096951fc | ||
|
97967cbe14 | ||
|
a6e09c9249 | ||
|
000e25a4b2 | ||
|
7a1cef46b0 | ||
|
a5412dd4a0 | ||
|
f81f66c98b | ||
|
43ed8f58f0 | ||
|
7727b359c8 | ||
|
8e4e710e15 | ||
|
4d4686d9be | ||
|
6d06ee8078 | ||
|
aec1ca6635 | ||
|
235a653f78 | ||
|
d030c78d1c | ||
|
90a9df9b8c | ||
|
070bd40b4c | ||
|
46de3ae9a9 | ||
|
b6833e478d | ||
|
73b0a0ed0e | ||
|
7c4515d748 | ||
|
748c20c979 | ||
|
3573ac818f | ||
|
ed4bce3285 | ||
|
95b8147fa1 | ||
|
f5ac238231 | ||
|
bfdec9e2e3 |
197
agent/README.md
197
agent/README.md
@@ -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)
|
||||
}
|
||||
}
|
||||
```
|
@@ -1,18 +0,0 @@
|
||||
# Go API [](https://opensource.org/licenses/Apache-2.0) [](https://godoc.org/github.com/micro/go-micro/api) [](https://travis-ci.org/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.
|
||||
|
||||
<img src="https://micro.mu/docs/images/go-api.png?v=1" alt="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
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package memory
|
||||
package broker
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
var (
|
||||
// mock data
|
||||
Data = map[string][]*registry.Service{
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
@@ -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),
|
||||
},
|
||||
|
@@ -14,7 +14,7 @@ import (
|
||||
func newTestRegistry() *memory.Registry {
|
||||
r := memory.NewRegistry()
|
||||
m := r.(*memory.Registry)
|
||||
m.Setup()
|
||||
m.Services = testData
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -1,14 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *buffer) Close() error {
|
||||
b.Buffer.Reset()
|
||||
return nil
|
||||
}
|
@@ -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
|
||||
|
47
client/common_test.go
Normal file
47
client/common_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// mock data
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.0-123",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
{
|
||||
Id: "foo-1.0.0-321",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.1-321",
|
||||
Address: "localhost:6666",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.3-345",
|
||||
Address: "localhost:8888",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
@@ -1,14 +0,0 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *buffer) Close() error {
|
||||
b.Buffer.Reset()
|
||||
return nil
|
||||
}
|
@@ -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()
|
||||
}
|
||||
|
@@ -2,22 +2,23 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
@@ -31,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
|
||||
@@ -48,17 +50,29 @@ func (g *grpcClient) secure() grpc.DialOption {
|
||||
}
|
||||
|
||||
func (g *grpcClient) next(request client.Request, opts client.CallOptions) (selector.Next, error) {
|
||||
service := request.Service()
|
||||
|
||||
// get proxy
|
||||
if prx := os.Getenv("MICRO_PROXY"); len(prx) > 0 {
|
||||
service = prx
|
||||
}
|
||||
|
||||
// get proxy address
|
||||
if prx := os.Getenv("MICRO_PROXY_ADDRESS"); len(prx) > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// get next nodes from the selector
|
||||
next, err := g.opts.Selector.Select(request.Service(), opts.SelectOptions...)
|
||||
next, err := g.opts.Selector.Select(service, opts.SelectOptions...)
|
||||
if err != nil && err == selector.ErrNotFound {
|
||||
return nil, errors.NotFound("go.micro.client", err.Error())
|
||||
} else if err != nil {
|
||||
@@ -70,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 {
|
||||
@@ -99,7 +110,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
|
||||
|
||||
var grr error
|
||||
|
||||
cc, err := g.pool.getConn(address, grpc.WithDefaultCallOptions(grpc.CallCustomCodec(cf)),
|
||||
cc, err := g.pool.getConn(address, grpc.WithDefaultCallOptions(grpc.ForceCodec(cf)),
|
||||
grpc.WithTimeout(opts.DialTimeout), g.secure(),
|
||||
grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
|
||||
@@ -116,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.CallContentSubtype(cf.String()))
|
||||
err := cc.Invoke(ctx, methodToGRPC(req.Service(), req.Endpoint()), req.Body(), rsp, grpc.CallContentSubtype(cf.Name()))
|
||||
ch <- microError(err)
|
||||
}()
|
||||
|
||||
@@ -132,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 {
|
||||
@@ -164,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.CallCustomCodec(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))
|
||||
}
|
||||
@@ -175,16 +186,34 @@ 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()), grpc.CallContentSubtype(cf.String()))
|
||||
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{
|
||||
context: ctx,
|
||||
request: req,
|
||||
stream: st,
|
||||
conn: cc,
|
||||
context: ctx,
|
||||
request: req,
|
||||
response: rsp,
|
||||
stream: st,
|
||||
conn: cc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -210,7 +239,7 @@ func (g *grpcClient) maxSendMsgSizeValue() int {
|
||||
return v.(int)
|
||||
}
|
||||
|
||||
func (g *grpcClient) newGRPCCodec(contentType string) (grpc.Codec, error) {
|
||||
func (g *grpcClient) newGRPCCodec(contentType string) (encoding.Codec, error) {
|
||||
codecs := make(map[string]encoding.Codec)
|
||||
if g.opts.Context != nil {
|
||||
if v := g.opts.Context.Value(codecsKey{}); v != nil {
|
||||
@@ -260,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 {
|
||||
@@ -337,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():
|
||||
@@ -420,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():
|
||||
@@ -462,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())
|
||||
}
|
||||
|
||||
|
@@ -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",
|
||||
})
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package grpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
@@ -15,32 +14,28 @@ type grpcRequest struct {
|
||||
contentType string
|
||||
request interface{}
|
||||
opts client.RequestOptions
|
||||
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 {
|
||||
@@ -80,7 +75,7 @@ func (g *grpcRequest) Endpoint() string {
|
||||
}
|
||||
|
||||
func (g *grpcRequest) Codec() codec.Writer {
|
||||
return nil
|
||||
return g.codec
|
||||
}
|
||||
|
||||
func (g *grpcRequest) Body() interface{} {
|
||||
|
@@ -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)
|
||||
}
|
||||
|
44
client/grpc/response.go
Normal file
44
client/grpc/response.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/codec/bytes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding"
|
||||
)
|
||||
|
||||
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 r.gcodec
|
||||
}
|
||||
|
||||
// read the header
|
||||
func (r *response) Header() map[string]string {
|
||||
md, err := r.stream.Header()
|
||||
if err != nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
hdr := make(map[string]string)
|
||||
for k, v := range md {
|
||||
hdr[k] = strings.Join(v, ",")
|
||||
}
|
||||
return hdr
|
||||
}
|
||||
|
||||
// Read the undecoded response
|
||||
func (r *response) Read() ([]byte, error) {
|
||||
f := &bytes.Frame{}
|
||||
if err := r.gcodec.ReadBody(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.Data, nil
|
||||
}
|
@@ -12,11 +12,12 @@ import (
|
||||
// Implements the streamer interface
|
||||
type grpcStream struct {
|
||||
sync.RWMutex
|
||||
err error
|
||||
conn *grpc.ClientConn
|
||||
request client.Request
|
||||
stream grpc.ClientStream
|
||||
context context.Context
|
||||
err error
|
||||
conn *grpc.ClientConn
|
||||
stream grpc.ClientStream
|
||||
request client.Request
|
||||
response client.Response
|
||||
context context.Context
|
||||
}
|
||||
|
||||
func (g *grpcStream) Context() context.Context {
|
||||
@@ -28,7 +29,7 @@ func (g *grpcStream) Request() client.Request {
|
||||
}
|
||||
|
||||
func (g *grpcStream) Response() client.Response {
|
||||
return nil
|
||||
return g.response
|
||||
}
|
||||
|
||||
func (g *grpcStream) Send(msg interface{}) error {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Package rpc provides an rpc client
|
||||
package rpc
|
||||
// Package mucp provides an mucp client
|
||||
package mucp
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/client"
|
@@ -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
|
||||
}
|
||||
|
114
client/pool/default.go
Normal file
114
client/pool/default.go
Normal file
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
33
client/pool/options.go
Normal file
33
client/pool/options.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
35
client/pool/pool.go
Normal file
35
client/pool/pool.go
Normal file
@@ -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)
|
||||
}
|
203
client/proto/client.micro.go
Normal file
203
client/proto/client.micro.go
Normal file
@@ -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)
|
||||
}
|
388
client/proto/client.pb.go
Normal file
388
client/proto/client.pb.go
Normal file
@@ -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",
|
||||
}
|
30
client/proto/client.proto
Normal file
30
client/proto/client.proto
Normal file
@@ -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;
|
||||
}
|
@@ -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(),
|
||||
@@ -567,5 +571,5 @@ func (r *rpcClient) NewRequest(service, method string, request interface{}, reqO
|
||||
}
|
||||
|
||||
func (r *rpcClient) String() string {
|
||||
return "rpc"
|
||||
return "mucp"
|
||||
}
|
||||
|
@@ -5,24 +5,24 @@ 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 {
|
||||
r := memory.NewRegistry()
|
||||
r.(*memory.Registry).Setup()
|
||||
return r
|
||||
reg := r.(*memory.Registry)
|
||||
reg.Services = testData
|
||||
return reg
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -40,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
|
||||
}
|
||||
@@ -59,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)
|
||||
}
|
||||
|
||||
@@ -113,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 {
|
||||
@@ -151,7 +146,6 @@ func TestCallWrapper(t *testing.T) {
|
||||
®istry.Node{
|
||||
Id: id,
|
||||
Address: address,
|
||||
Port: port,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@@ -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()
|
||||
}
|
47
client/selector/common_test.go
Normal file
47
client/selector/common_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// mock data
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.0-123",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
{
|
||||
Id: "foo-1.0.0-321",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.1-321",
|
||||
Address: "localhost:6666",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.3-345",
|
||||
Address: "localhost:8888",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
@@ -10,7 +10,8 @@ func TestRegistrySelector(t *testing.T) {
|
||||
counts := map[string]int{}
|
||||
|
||||
r := memory.NewRegistry()
|
||||
r.(*memory.Registry).Setup()
|
||||
rg := r.(*memory.Registry)
|
||||
rg.Services = testData
|
||||
cache := NewSelector(Registry(r))
|
||||
|
||||
next, err := cache.Select("foo")
|
@@ -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),
|
||||
})
|
||||
}
|
||||
|
@@ -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
|
@@ -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
|
272
client/selector/router/router.go
Normal file
272
client/selector/router/router.go
Normal file
@@ -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/network/router"
|
||||
pb "github.com/micro/go-micro/network/router/proto"
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
@@ -9,7 +9,7 @@ const (
|
||||
Error MessageType = iota
|
||||
Request
|
||||
Response
|
||||
Publication
|
||||
Event
|
||||
)
|
||||
|
||||
type MessageType int
|
||||
|
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/micro/go-micro/codec"
|
||||
)
|
||||
|
||||
@@ -22,6 +24,9 @@ func (c *Codec) ReadBody(b interface{}) error {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
if pb, ok := b.(proto.Message); ok {
|
||||
return jsonpb.UnmarshalNext(c.Decoder, pb)
|
||||
}
|
||||
return c.Decoder.Decode(b)
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,9 @@ package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
type Marshaler struct{}
|
||||
@@ -11,6 +14,9 @@ func (j Marshaler) Marshal(v interface{}) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (j Marshaler) Unmarshal(d []byte, v interface{}) error {
|
||||
if pb, ok := v.(proto.Message); ok {
|
||||
return jsonpb.UnmarshalString(string(d), pb)
|
||||
}
|
||||
return json.Unmarshal(d, v)
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -30,6 +30,9 @@ func (c *Codec) ReadBody(b interface{}) error {
|
||||
}
|
||||
|
||||
switch b.(type) {
|
||||
case *string:
|
||||
v := b.(*string)
|
||||
*v = string(buf)
|
||||
case *[]byte:
|
||||
v := b.(*[]byte)
|
||||
*v = buf
|
||||
@@ -51,6 +54,12 @@ func (c *Codec) Write(m *codec.Message, b interface{}) error {
|
||||
case *[]byte:
|
||||
ve := b.(*[]byte)
|
||||
v = *ve
|
||||
case *string:
|
||||
ve := b.(*string)
|
||||
v = []byte(*ve)
|
||||
case string:
|
||||
ve := b.(string)
|
||||
v = []byte(ve)
|
||||
case []byte:
|
||||
v = b.([]byte)
|
||||
default:
|
||||
@@ -65,7 +74,7 @@ func (c *Codec) Close() error {
|
||||
}
|
||||
|
||||
func (c *Codec) String() string {
|
||||
return "bytes"
|
||||
return "text"
|
||||
}
|
||||
|
||||
func NewCodec(c io.ReadWriteCloser) codec.Codec {
|
||||
|
47
common_test.go
Normal file
47
common_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// mock data
|
||||
testData = map[string][]*registry.Service{
|
||||
"foo": []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.0-123",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
{
|
||||
Id: "foo-1.0.0-321",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.1-321",
|
||||
Address: "localhost:6666",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-1.0.3-345",
|
||||
Address: "localhost:8888",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
@@ -1,6 +1,6 @@
|
||||
# Config [](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.
|
||||
|
@@ -11,7 +11,11 @@ import (
|
||||
|
||||
"github.com/micro/cli"
|
||||
"github.com/micro/go-micro/client"
|
||||
cgrpc "github.com/micro/go-micro/client/grpc"
|
||||
cmucp "github.com/micro/go-micro/client/mucp"
|
||||
"github.com/micro/go-micro/server"
|
||||
sgrpc "github.com/micro/go-micro/server/grpc"
|
||||
smucp "github.com/micro/go-micro/server/mucp"
|
||||
"github.com/micro/go-micro/util/log"
|
||||
|
||||
// brokers
|
||||
@@ -28,9 +32,10 @@ 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"
|
||||
@@ -176,7 +181,9 @@ var (
|
||||
}
|
||||
|
||||
DefaultClients = map[string]func(...client.Option) client.Client{
|
||||
"rpc": client.NewClient,
|
||||
"rpc": client.NewClient,
|
||||
"mucp": cmucp.NewClient,
|
||||
"grpc": cgrpc.NewClient,
|
||||
}
|
||||
|
||||
DefaultRegistries = map[string]func(...registry.Option) registry.Registry{
|
||||
@@ -190,11 +197,14 @@ 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,
|
||||
}
|
||||
|
||||
DefaultTransports = map[string]func(...transport.Option) transport.Transport{
|
@@ -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"
|
||||
)
|
@@ -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")
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,51 +0,0 @@
|
||||
# Etcd Source
|
||||
|
||||
The etcd source reads config from etcd key/values
|
||||
|
||||
This source supports etcd version 3 and beyond.
|
||||
|
||||
## Etcd Format
|
||||
|
||||
The etcd source expects keys under the default prefix `/micro/config` (prefix can be changed)
|
||||
|
||||
Values are expected to be JSON
|
||||
|
||||
```
|
||||
// set database
|
||||
etcdctl put /micro/config/database '{"address": "10.0.0.1", "port": 3306}'
|
||||
// set cache
|
||||
etcdctl put /micro/config/cache '{"address": "10.0.0.2", "port": 6379}'
|
||||
```
|
||||
|
||||
Keys are split on `/` so access becomes
|
||||
|
||||
```
|
||||
conf.Get("micro", "config", "database")
|
||||
```
|
||||
|
||||
## New Source
|
||||
|
||||
Specify source with data
|
||||
|
||||
```go
|
||||
etcdSource := etcd.NewSource(
|
||||
// optionally specify etcd address; default to localhost:8500
|
||||
etcd.WithAddress("10.0.0.10:8500"),
|
||||
// optionally specify prefix; defaults to /micro/config
|
||||
etcd.WithPrefix("/my/prefix"),
|
||||
// optionally strip the provided prefix from the keys, defaults to false
|
||||
etcd.StripPrefix(true),
|
||||
)
|
||||
```
|
||||
|
||||
## Load Source
|
||||
|
||||
Load the source into config
|
||||
|
||||
```go
|
||||
// Create new config
|
||||
conf := config.NewConfig()
|
||||
|
||||
// Load file source
|
||||
conf.Load(etcdSource)
|
||||
```
|
@@ -1,134 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/config/source"
|
||||
cetcd "go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
// Currently a single etcd reader
|
||||
type etcd struct {
|
||||
prefix string
|
||||
stripPrefix string
|
||||
opts source.Options
|
||||
client *cetcd.Client
|
||||
cerr error
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultPrefix = "/micro/config/"
|
||||
)
|
||||
|
||||
func (c *etcd) Read() (*source.ChangeSet, error) {
|
||||
if c.cerr != nil {
|
||||
return nil, c.cerr
|
||||
}
|
||||
|
||||
rsp, err := c.client.Get(context.Background(), c.prefix, cetcd.WithPrefix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rsp == nil || len(rsp.Kvs) == 0 {
|
||||
return nil, fmt.Errorf("source not found: %s", c.prefix)
|
||||
}
|
||||
|
||||
var kvs []*mvccpb.KeyValue
|
||||
for _, v := range rsp.Kvs {
|
||||
kvs = append(kvs, (*mvccpb.KeyValue)(v))
|
||||
}
|
||||
|
||||
data := makeMap(c.opts.Encoder, kvs, c.stripPrefix)
|
||||
|
||||
b, err := c.opts.Encoder.Encode(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading source: %v", err)
|
||||
}
|
||||
|
||||
cs := &source.ChangeSet{
|
||||
Timestamp: time.Now(),
|
||||
Source: c.String(),
|
||||
Data: b,
|
||||
Format: c.opts.Encoder.String(),
|
||||
}
|
||||
cs.Checksum = cs.Sum()
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (c *etcd) String() string {
|
||||
return "etcd"
|
||||
}
|
||||
|
||||
func (c *etcd) Watch() (source.Watcher, error) {
|
||||
if c.cerr != nil {
|
||||
return nil, c.cerr
|
||||
}
|
||||
cs, err := c.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWatcher(c.prefix, c.stripPrefix, c.client.Watcher, cs, c.opts)
|
||||
}
|
||||
|
||||
func NewSource(opts ...source.Option) source.Source {
|
||||
options := source.NewOptions(opts...)
|
||||
|
||||
var endpoints []string
|
||||
|
||||
// check if there are any addrs
|
||||
addrs, ok := options.Context.Value(addressKey{}).([]string)
|
||||
if ok {
|
||||
for _, a := range addrs {
|
||||
addr, port, err := net.SplitHostPort(a)
|
||||
if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
|
||||
port = "2379"
|
||||
addr = a
|
||||
endpoints = append(endpoints, fmt.Sprintf("%s:%s", addr, port))
|
||||
} else if err == nil {
|
||||
endpoints = append(endpoints, fmt.Sprintf("%s:%s", addr, port))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(endpoints) == 0 {
|
||||
endpoints = []string{"localhost:2379"}
|
||||
}
|
||||
|
||||
config := cetcd.Config{
|
||||
Endpoints: endpoints,
|
||||
}
|
||||
|
||||
u, ok := options.Context.Value(authKey{}).(*authCreds)
|
||||
if ok {
|
||||
config.Username = u.Username
|
||||
config.Password = u.Password
|
||||
}
|
||||
|
||||
// use default config
|
||||
client, err := cetcd.New(config)
|
||||
|
||||
prefix := DefaultPrefix
|
||||
sp := ""
|
||||
f, ok := options.Context.Value(prefixKey{}).(string)
|
||||
if ok {
|
||||
prefix = f
|
||||
}
|
||||
|
||||
if b, ok := options.Context.Value(stripPrefixKey{}).(bool); ok && b {
|
||||
sp = prefix
|
||||
}
|
||||
|
||||
return &etcd{
|
||||
prefix: prefix,
|
||||
stripPrefix: sp,
|
||||
opts: options,
|
||||
client: client,
|
||||
cerr: err,
|
||||
}
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/config/source"
|
||||
)
|
||||
|
||||
type addressKey struct{}
|
||||
type prefixKey struct{}
|
||||
type stripPrefixKey struct{}
|
||||
type authKey struct{}
|
||||
|
||||
type authCreds struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// WithAddress sets the consul address
|
||||
func WithAddress(a ...string) source.Option {
|
||||
return func(o *source.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, addressKey{}, a)
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrefix sets the key prefix to use
|
||||
func WithPrefix(p string) source.Option {
|
||||
return func(o *source.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, prefixKey{}, p)
|
||||
}
|
||||
}
|
||||
|
||||
// StripPrefix indicates whether to remove the prefix from config entries, or leave it in place.
|
||||
func StripPrefix(strip bool) source.Option {
|
||||
return func(o *source.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
|
||||
o.Context = context.WithValue(o.Context, stripPrefixKey{}, strip)
|
||||
}
|
||||
}
|
||||
|
||||
// Auth allows you to specify username/password
|
||||
func Auth(username, password string) source.Option {
|
||||
return func(o *source.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, authKey{}, &authCreds{Username: username, Password: password})
|
||||
}
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/config/encoder"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
func makeEvMap(e encoder.Encoder, data map[string]interface{}, kv []*clientv3.Event, stripPrefix string) map[string]interface{} {
|
||||
if data == nil {
|
||||
data = make(map[string]interface{})
|
||||
}
|
||||
|
||||
for _, v := range kv {
|
||||
switch mvccpb.Event_EventType(v.Type) {
|
||||
case mvccpb.DELETE:
|
||||
data = update(e, data, (*mvccpb.KeyValue)(v.Kv), "delete", stripPrefix)
|
||||
default:
|
||||
data = update(e, data, (*mvccpb.KeyValue)(v.Kv), "insert", stripPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func makeMap(e encoder.Encoder, kv []*mvccpb.KeyValue, stripPrefix string) map[string]interface{} {
|
||||
data := make(map[string]interface{})
|
||||
|
||||
for _, v := range kv {
|
||||
data = update(e, data, v, "put", stripPrefix)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func update(e encoder.Encoder, data map[string]interface{}, v *mvccpb.KeyValue, action, stripPrefix string) map[string]interface{} {
|
||||
// remove prefix if non empty, and ensure leading / is removed as well
|
||||
vkey := strings.TrimPrefix(strings.TrimPrefix(string(v.Key), stripPrefix), "/")
|
||||
// split on prefix
|
||||
haveSplit := strings.Contains(vkey, "/")
|
||||
keys := strings.Split(vkey, "/")
|
||||
|
||||
var vals interface{}
|
||||
e.Decode(v.Value, &vals)
|
||||
|
||||
if !haveSplit && len(keys) == 1 {
|
||||
switch action {
|
||||
case "delete":
|
||||
data = make(map[string]interface{})
|
||||
default:
|
||||
v, ok := vals.(map[string]interface{})
|
||||
if ok {
|
||||
data = v
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// set data for first iteration
|
||||
kvals := data
|
||||
// iterate the keys and make maps
|
||||
for i, k := range keys {
|
||||
kval, ok := kvals[k].(map[string]interface{})
|
||||
if !ok {
|
||||
// create next map
|
||||
kval = make(map[string]interface{})
|
||||
// set it
|
||||
kvals[k] = kval
|
||||
}
|
||||
|
||||
// last key: write vals
|
||||
if l := len(keys) - 1; i == l {
|
||||
switch action {
|
||||
case "delete":
|
||||
delete(kvals, k)
|
||||
default:
|
||||
kvals[k] = vals
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// set kvals for next iterator
|
||||
kvals = kval
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
@@ -1,113 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/config/source"
|
||||
cetcd "go.etcd.io/etcd/clientv3"
|
||||
)
|
||||
|
||||
type watcher struct {
|
||||
opts source.Options
|
||||
name string
|
||||
stripPrefix string
|
||||
|
||||
sync.RWMutex
|
||||
cs *source.ChangeSet
|
||||
|
||||
ch chan *source.ChangeSet
|
||||
exit chan bool
|
||||
}
|
||||
|
||||
func newWatcher(key, strip string, wc cetcd.Watcher, cs *source.ChangeSet, opts source.Options) (source.Watcher, error) {
|
||||
w := &watcher{
|
||||
opts: opts,
|
||||
name: "etcd",
|
||||
stripPrefix: strip,
|
||||
cs: cs,
|
||||
ch: make(chan *source.ChangeSet),
|
||||
exit: make(chan bool),
|
||||
}
|
||||
|
||||
ch := wc.Watch(context.Background(), key, cetcd.WithPrefix())
|
||||
|
||||
go w.run(wc, ch)
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *watcher) handle(evs []*cetcd.Event) {
|
||||
w.RLock()
|
||||
data := w.cs.Data
|
||||
w.RUnlock()
|
||||
|
||||
var vals map[string]interface{}
|
||||
|
||||
// unpackage existing changeset
|
||||
if err := w.opts.Encoder.Decode(data, &vals); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// update base changeset
|
||||
d := makeEvMap(w.opts.Encoder, vals, evs, w.stripPrefix)
|
||||
|
||||
// pack the changeset
|
||||
b, err := w.opts.Encoder.Encode(d)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// create new changeset
|
||||
cs := &source.ChangeSet{
|
||||
Timestamp: time.Now(),
|
||||
Source: w.name,
|
||||
Data: b,
|
||||
Format: w.opts.Encoder.String(),
|
||||
}
|
||||
cs.Checksum = cs.Sum()
|
||||
|
||||
// set base change set
|
||||
w.Lock()
|
||||
w.cs = cs
|
||||
w.Unlock()
|
||||
|
||||
// send update
|
||||
w.ch <- cs
|
||||
}
|
||||
|
||||
func (w *watcher) run(wc cetcd.Watcher, ch cetcd.WatchChan) {
|
||||
for {
|
||||
select {
|
||||
case rsp, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.handle(rsp.Events)
|
||||
case <-w.exit:
|
||||
wc.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) Next() (*source.ChangeSet, error) {
|
||||
select {
|
||||
case cs := <-w.ch:
|
||||
return cs, nil
|
||||
case <-w.exit:
|
||||
return nil, errors.New("watcher stopped")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) Stop() error {
|
||||
select {
|
||||
case <-w.exit:
|
||||
return nil
|
||||
default:
|
||||
close(w.exit)
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -6,24 +6,26 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/micro/go-micro/sync/data"
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/data/store"
|
||||
)
|
||||
|
||||
type ckv struct {
|
||||
options.Options
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
func (c *ckv) Read(key string) (*data.Record, error) {
|
||||
func (c *ckv) Read(key string) (*store.Record, error) {
|
||||
keyval, _, err := c.client.KV().Get(key, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if keyval == nil {
|
||||
return nil, data.ErrNotFound
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
|
||||
return &data.Record{
|
||||
return &store.Record{
|
||||
Key: keyval.Key,
|
||||
Value: keyval.Value,
|
||||
}, nil
|
||||
@@ -34,7 +36,7 @@ func (c *ckv) Delete(key string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ckv) Write(record *data.Record) error {
|
||||
func (c *ckv) Write(record *store.Record) error {
|
||||
_, err := c.client.KV().Put(&api.KVPair{
|
||||
Key: record.Key,
|
||||
Value: record.Value,
|
||||
@@ -42,17 +44,17 @@ func (c *ckv) Write(record *data.Record) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ckv) Dump() ([]*data.Record, error) {
|
||||
func (c *ckv) Dump() ([]*store.Record, error) {
|
||||
keyval, _, err := c.client.KV().List("/", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keyval == nil {
|
||||
return nil, data.ErrNotFound
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
var vals []*data.Record
|
||||
var vals []*store.Record
|
||||
for _, keyv := range keyval {
|
||||
vals = append(vals, &data.Record{
|
||||
vals = append(vals, &store.Record{
|
||||
Key: keyv.Key,
|
||||
Value: keyv.Value,
|
||||
})
|
||||
@@ -64,22 +66,22 @@ func (c *ckv) String() string {
|
||||
return "consul"
|
||||
}
|
||||
|
||||
func NewData(opts ...data.Option) data.Data {
|
||||
var options data.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
func NewStore(opts ...options.Option) store.Store {
|
||||
options := options.NewOptions(opts...)
|
||||
config := api.DefaultConfig()
|
||||
|
||||
var nodes []string
|
||||
|
||||
if n, ok := options.Values().Get("store.nodes"); ok {
|
||||
nodes = n.([]string)
|
||||
}
|
||||
|
||||
// set host
|
||||
// config.Host something
|
||||
// check if there are any addrs
|
||||
if len(options.Nodes) > 0 {
|
||||
addr, port, err := net.SplitHostPort(options.Nodes[0])
|
||||
if len(nodes) > 0 {
|
||||
addr, port, err := net.SplitHostPort(nodes[0])
|
||||
if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
|
||||
port = "8500"
|
||||
config.Address = fmt.Sprintf("%s:%s", options.Nodes[0], port)
|
||||
config.Address = fmt.Sprintf("%s:%s", nodes[0], port)
|
||||
} else if err == nil {
|
||||
config.Address = fmt.Sprintf("%s:%s", addr, port)
|
||||
}
|
||||
@@ -88,6 +90,7 @@ func NewData(opts ...data.Option) data.Data {
|
||||
client, _ := api.NewClient(config)
|
||||
|
||||
return &ckv{
|
||||
client: client,
|
||||
Options: options,
|
||||
client: client,
|
||||
}
|
||||
}
|
108
data/store/memory/memory.go
Normal file
108
data/store/memory/memory.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Package memory is a in-memory store store
|
||||
package memory
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/data/store"
|
||||
)
|
||||
|
||||
type memoryStore struct {
|
||||
options.Options
|
||||
|
||||
sync.RWMutex
|
||||
values map[string]*memoryRecord
|
||||
}
|
||||
|
||||
type memoryRecord struct {
|
||||
r *store.Record
|
||||
c time.Time
|
||||
}
|
||||
|
||||
func (m *memoryStore) Dump() ([]*store.Record, error) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
var values []*store.Record
|
||||
|
||||
for _, v := range m.values {
|
||||
// get expiry
|
||||
d := v.r.Expiry
|
||||
t := time.Since(v.c)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Read(key string) (*store.Record, error) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
v, ok := m.values[key]
|
||||
if !ok {
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
|
||||
// get expiry
|
||||
d := v.r.Expiry
|
||||
t := time.Since(v.c)
|
||||
|
||||
// expired
|
||||
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
|
||||
}
|
||||
|
||||
func (m *memoryStore) Write(r *store.Record) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// set the record
|
||||
m.values[r.Key] = &memoryRecord{
|
||||
r: r,
|
||||
c: time.Now(),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Delete(key string) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// delete the value
|
||||
delete(m.values, key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewStore returns a new store.Store
|
||||
func NewStore(opts ...options.Option) store.Store {
|
||||
options := options.NewOptions(opts...)
|
||||
|
||||
return &memoryStore{
|
||||
Options: options,
|
||||
values: make(map[string]*memoryRecord),
|
||||
}
|
||||
}
|
37
data/store/memory/memory_test.go
Normal file
37
data/store/memory/memory_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
15
data/store/options.go
Normal file
15
data/store/options.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/config/options"
|
||||
)
|
||||
|
||||
// Set the nodes used to back the store
|
||||
func Nodes(a ...string) options.Option {
|
||||
return options.WithValue("store.nodes", a)
|
||||
}
|
||||
|
||||
// Prefix sets a prefix to any key ids used
|
||||
func Prefix(p string) options.Option {
|
||||
return options.WithValue("store.prefix", p)
|
||||
}
|
@@ -1,17 +1,21 @@
|
||||
// Package data is an interface for key-value storage.
|
||||
package data
|
||||
// Package store is an interface for distribute data storage.
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/config/options"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
// Data is a data storage interface
|
||||
type Data interface {
|
||||
// Store is a data storage interface
|
||||
type Store interface {
|
||||
// embed options
|
||||
options.Options
|
||||
// Dump the known records
|
||||
Dump() ([]*Record, error)
|
||||
// Read a record with key
|
||||
@@ -24,9 +28,7 @@ type Data interface {
|
||||
|
||||
// Record represents a data record
|
||||
type Record struct {
|
||||
Key string
|
||||
Value []byte
|
||||
Expiration time.Duration
|
||||
Key string
|
||||
Value []byte
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
type Option func(o *Options)
|
@@ -14,7 +14,7 @@ func TestFunction(t *testing.T) {
|
||||
wg.Add(1)
|
||||
|
||||
r := memory.NewRegistry()
|
||||
r.(*memory.Registry).Setup()
|
||||
r.(*memory.Registry).Services = testData
|
||||
|
||||
// create service
|
||||
fn := NewFunction(
|
||||
|
62
go.mod
62
go.mod
@@ -3,44 +3,82 @@ module github.com/micro/go-micro
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.41.0 // indirect
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
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/coreos/etcd v3.3.13+incompatible // indirect
|
||||
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/ghodss/yaml v1.0.0
|
||||
github.com/gliderlabs/ssh v0.2.2 // indirect
|
||||
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/uuid v1.1.1
|
||||
github.com/gorilla/handlers v1.4.0
|
||||
github.com/gorilla/handlers v1.4.1
|
||||
github.com/gorilla/mux v1.7.3 // indirect
|
||||
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/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/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c // indirect
|
||||
github.com/kisielk/errcheck v1.2.0 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/kr/pty v1.1.8 // 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.7.1-0.20190710050138-1441923ab031
|
||||
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.1.1-0.20190729112526-ef68c9635478
|
||||
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/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/common v0.6.0 // indirect
|
||||
github.com/prometheus/procfs v0.0.3 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2 // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
go.etcd.io/etcd v3.3.13+incompatible
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
|
||||
golang.org/x/net v0.0.0-20190606173856-1492cefac77f
|
||||
google.golang.org/grpc v1.21.1
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
||||
golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f // indirect
|
||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9 // indirect
|
||||
golang.org/x/mod v0.1.0 // indirect
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7
|
||||
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 // indirect
|
||||
golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 // indirect
|
||||
google.golang.org/grpc v1.22.0
|
||||
gopkg.in/go-playground/validator.v9 v9.29.0
|
||||
gopkg.in/src-d/go-git.v4 v4.11.0
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.1 // indirect
|
||||
gopkg.in/src-d/go-git.v4 v4.12.0
|
||||
gopkg.in/telegram-bot-api.v4 v4.6.4
|
||||
honnef.co/go/tools v0.0.0-20190614002413-cb51c254f01b // indirect
|
||||
)
|
||||
|
244
go.sum
244
go.sum
@@ -1,16 +1,31 @@
|
||||
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=
|
||||
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/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/units v0.0.0-20151022065526-2efee857e7cf/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/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,11 +33,14 @@ 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/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
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/docker v0.7.3-0.20190309235953-33c3200e0d16 h1:dmUn0SuGx7unKFwxyeQ/oLUHhEfZosEDrpmYM+6MTuc=
|
||||
@@ -33,6 +51,8 @@ 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=
|
||||
@@ -44,30 +64,52 @@ github.com/fsouza/go-dockerclient v1.4.1/go.mod h1:PUNHxbowDqRXfRgZqMz1OeGtbWC6V
|
||||
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/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/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/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=
|
||||
@@ -75,33 +117,51 @@ 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/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=
|
||||
@@ -114,34 +174,65 @@ 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/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/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/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/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/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=
|
||||
@@ -152,17 +243,24 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
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/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=
|
||||
@@ -170,6 +268,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=
|
||||
@@ -177,77 +276,205 @@ 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_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.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=
|
||||
go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw=
|
||||
go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
|
||||
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/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/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/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-20190606173856-1492cefac77f h1:IWHgpgFqnL5AhBUBZSgBdjl2vkQUEzcY+JNKWfcgAU0=
|
||||
golang.org/x/net v0.0.0-20190606173856-1492cefac77f/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
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/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/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-20190624190245-7f2218787638/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=
|
||||
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/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=
|
||||
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/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-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/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=
|
||||
@@ -258,3 +485,8 @@ 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=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
288
network/link/default.go
Normal file
288
network/link/default.go
Normal file
@@ -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"
|
||||
}
|
||||
}
|
60
network/link/link.go
Normal file
60
network/link/link.go
Normal file
@@ -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)
|
||||
}
|
@@ -9,9 +9,8 @@ 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/options"
|
||||
"github.com/micro/go-micro/proxy"
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/network/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...)
|
@@ -10,9 +10,9 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/errors"
|
||||
"github.com/micro/go-micro/options"
|
||||
"github.com/micro/go-micro/proxy"
|
||||
"github.com/micro/go-micro/network/proxy"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
325
network/proxy/mucp/mucp.go
Normal file
325
network/proxy/mucp/mucp.go
Normal file
@@ -0,0 +1,325 @@
|
||||
// Package mucp transparently forwards the incoming request using a go-micro client.
|
||||
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/network/proxy"
|
||||
"github.com/micro/go-micro/network/router"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// Proxy will transparently proxy requests to an endpoint.
|
||||
// If no endpoint is specified it will call a service using the client.
|
||||
type Proxy struct {
|
||||
// embed options
|
||||
options.Options
|
||||
|
||||
// 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
|
||||
func readLoop(r server.Request, s client.Stream) error {
|
||||
// request to backend server
|
||||
req := s.Request()
|
||||
|
||||
for {
|
||||
// get data from client
|
||||
// no need to decode it
|
||||
body, err := r.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the header from client
|
||||
hdr := r.Header()
|
||||
msg := &codec.Message{
|
||||
Type: codec.Request,
|
||||
Header: hdr,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// write the raw request
|
||||
err = req.Codec().Write(msg, nil)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 endpoint either by name or address
|
||||
if len(p.Endpoint) > 0 {
|
||||
// address:port
|
||||
if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 {
|
||||
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
|
||||
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()))
|
||||
|
||||
// create new stream
|
||||
stream, err := p.Client.Stream(ctx, creq, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
// create client request read loop
|
||||
go readLoop(req, stream)
|
||||
|
||||
// get raw response
|
||||
resp := stream.Response()
|
||||
|
||||
// route watcher error
|
||||
var watchErr error
|
||||
|
||||
// create server response write loop
|
||||
for {
|
||||
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()
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSingleHostProxy returns a proxy which sends requests to a single backend
|
||||
func NewSingleHostProxy(endpoint string) *Proxy {
|
||||
return &Proxy{
|
||||
Options: options.NewOptions(),
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewProxy returns a new proxy which will route based on mucp headers
|
||||
func NewProxy(opts ...options.Option) proxy.Proxy {
|
||||
p := new(Proxy)
|
||||
p.Options = options.NewOptions(opts...)
|
||||
p.Options.Init(options.WithString("mucp"))
|
||||
|
||||
// get endpoint
|
||||
ep, ok := p.Options.Values().Get("proxy.endpoint")
|
||||
if ok {
|
||||
p.Endpoint = ep.(string)
|
||||
}
|
||||
|
||||
// get client
|
||||
c, ok := p.Options.Values().Get("proxy.client")
|
||||
if ok {
|
||||
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
|
||||
}
|
@@ -5,7 +5,8 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/options"
|
||||
"github.com/micro/go-micro/config/options"
|
||||
"github.com/micro/go-micro/network/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)
|
||||
}
|
30
network/resolver/dns/dns.go
Normal file
30
network/resolver/dns/dns.go
Normal file
@@ -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
|
||||
}
|
59
network/resolver/http/http.go
Normal file
59
network/resolver/http/http.go
Normal file
@@ -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
|
||||
}
|
37
network/resolver/registry/registry.go
Normal file
37
network/resolver/registry/registry.go
Normal file
@@ -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
|
||||
}
|
15
network/resolver/resolver.go
Normal file
15
network/resolver/resolver.go
Normal file
@@ -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"`
|
||||
}
|
684
network/router/default.go
Normal file
684
network/router/default.go
Normal file
@@ -0,0 +1,684 @@
|
||||
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)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
<-r.exit
|
||||
w.Stop()
|
||||
}()
|
||||
|
||||
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)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
<-r.exit
|
||||
w.Stop()
|
||||
}()
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
// 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"
|
||||
}
|
75
network/router/options.go
Normal file
75
network/router/options.go
Normal file
@@ -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,
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user