Moved to google.golang.org/genproto/googleapis/api/annotations
Fixes #52
This commit is contained in:
177
vendor/github.com/go-kit/kit/sd/etcd/client.go
generated
vendored
Normal file
177
vendor/github.com/go-kit/kit/sd/etcd/client.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoKey indicates a client method needs a key but receives none.
|
||||
ErrNoKey = errors.New("no key provided")
|
||||
|
||||
// ErrNoValue indicates a client method needs a value but receives none.
|
||||
ErrNoValue = errors.New("no value provided")
|
||||
)
|
||||
|
||||
// Client is a wrapper around the etcd client.
|
||||
type Client interface {
|
||||
// GetEntries queries the given prefix in etcd and returns a slice
|
||||
// containing the values of all keys found, recursively, underneath that
|
||||
// prefix.
|
||||
GetEntries(prefix string) ([]string, error)
|
||||
|
||||
// WatchPrefix watches the given prefix in etcd for changes. When a change
|
||||
// is detected, it will signal on the passed channel. Clients are expected
|
||||
// to call GetEntries to update themselves with the latest set of complete
|
||||
// values. WatchPrefix will always send an initial sentinel value on the
|
||||
// channel after establishing the watch, to ensure that clients always
|
||||
// receive the latest set of values. WatchPrefix will block until the
|
||||
// context passed to the NewClient constructor is terminated.
|
||||
WatchPrefix(prefix string, ch chan struct{})
|
||||
|
||||
// Register a service with etcd.
|
||||
Register(s Service) error
|
||||
|
||||
// Deregister a service with etcd.
|
||||
Deregister(s Service) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
keysAPI etcd.KeysAPI
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// ClientOptions defines options for the etcd client. All values are optional.
|
||||
// If any duration is not specified, a default of 3 seconds will be used.
|
||||
type ClientOptions struct {
|
||||
Cert string
|
||||
Key string
|
||||
CACert string
|
||||
DialTimeout time.Duration
|
||||
DialKeepAlive time.Duration
|
||||
HeaderTimeoutPerRequest time.Duration
|
||||
}
|
||||
|
||||
// NewClient returns Client with a connection to the named machines. It will
|
||||
// return an error if a connection to the cluster cannot be made. The parameter
|
||||
// machines needs to be a full URL with schemas. e.g. "http://localhost:2379"
|
||||
// will work, but "localhost:2379" will not.
|
||||
func NewClient(ctx context.Context, machines []string, options ClientOptions) (Client, error) {
|
||||
if options.DialTimeout == 0 {
|
||||
options.DialTimeout = 3 * time.Second
|
||||
}
|
||||
if options.DialKeepAlive == 0 {
|
||||
options.DialKeepAlive = 3 * time.Second
|
||||
}
|
||||
if options.HeaderTimeoutPerRequest == 0 {
|
||||
options.HeaderTimeoutPerRequest = 3 * time.Second
|
||||
}
|
||||
|
||||
transport := etcd.DefaultTransport
|
||||
if options.Cert != "" && options.Key != "" {
|
||||
tlsCert, err := tls.LoadX509KeyPair(options.Cert, options.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertCt, err := ioutil.ReadFile(options.CACert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCertCt)
|
||||
transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
RootCAs: caCertPool,
|
||||
},
|
||||
Dial: func(network, address string) (net.Conn, error) {
|
||||
return (&net.Dialer{
|
||||
Timeout: options.DialTimeout,
|
||||
KeepAlive: options.DialKeepAlive,
|
||||
}).Dial(network, address)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
ce, err := etcd.New(etcd.Config{
|
||||
Endpoints: machines,
|
||||
Transport: transport,
|
||||
HeaderTimeoutPerRequest: options.HeaderTimeoutPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &client{
|
||||
keysAPI: etcd.NewKeysAPI(ce),
|
||||
ctx: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetEntries implements the etcd Client interface.
|
||||
func (c *client) GetEntries(key string) ([]string, error) {
|
||||
resp, err := c.keysAPI.Get(c.ctx, key, &etcd.GetOptions{Recursive: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Special case. Note that it's possible that len(resp.Node.Nodes) == 0 and
|
||||
// resp.Node.Value is also empty, in which case the key is empty and we
|
||||
// should not return any entries.
|
||||
if len(resp.Node.Nodes) == 0 && resp.Node.Value != "" {
|
||||
return []string{resp.Node.Value}, nil
|
||||
}
|
||||
|
||||
entries := make([]string, len(resp.Node.Nodes))
|
||||
for i, node := range resp.Node.Nodes {
|
||||
entries[i] = node.Value
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// WatchPrefix implements the etcd Client interface.
|
||||
func (c *client) WatchPrefix(prefix string, ch chan struct{}) {
|
||||
watch := c.keysAPI.Watcher(prefix, &etcd.WatcherOptions{AfterIndex: 0, Recursive: true})
|
||||
ch <- struct{}{} // make sure caller invokes GetEntries
|
||||
for {
|
||||
if _, err := watch.Next(c.ctx); err != nil {
|
||||
return
|
||||
}
|
||||
ch <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Register(s Service) error {
|
||||
if s.Key == "" {
|
||||
return ErrNoKey
|
||||
}
|
||||
if s.Value == "" {
|
||||
return ErrNoValue
|
||||
}
|
||||
var err error
|
||||
if s.TTL != nil {
|
||||
_, err = c.keysAPI.Set(c.ctx, s.Key, s.Value, &etcd.SetOptions{
|
||||
PrevExist: etcd.PrevIgnore,
|
||||
TTL: s.TTL.ttl,
|
||||
})
|
||||
} else {
|
||||
_, err = c.keysAPI.Create(c.ctx, s.Key, s.Value)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) Deregister(s Service) error {
|
||||
if s.Key == "" {
|
||||
return ErrNoKey
|
||||
}
|
||||
_, err := c.keysAPI.Delete(c.ctx, s.Key, s.DeleteOptions)
|
||||
return err
|
||||
}
|
314
vendor/github.com/go-kit/kit/sd/etcd/client_test.go
generated
vendored
Normal file
314
vendor/github.com/go-kit/kit/sd/etcd/client_test.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
client, err := NewClient(
|
||||
context.Background(),
|
||||
[]string{"http://irrelevant:12345"},
|
||||
ClientOptions{
|
||||
DialTimeout: 2 * time.Second,
|
||||
DialKeepAlive: 2 * time.Second,
|
||||
HeaderTimeoutPerRequest: 2 * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating client: %v", err)
|
||||
}
|
||||
if client == nil {
|
||||
t.Fatal("expected new Client, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient should fail when providing invalid or missing endpoints.
|
||||
func TestOptions(t *testing.T) {
|
||||
a, err := NewClient(
|
||||
context.Background(),
|
||||
[]string{},
|
||||
ClientOptions{
|
||||
Cert: "",
|
||||
Key: "",
|
||||
CACert: "",
|
||||
DialTimeout: 2 * time.Second,
|
||||
DialKeepAlive: 2 * time.Second,
|
||||
HeaderTimeoutPerRequest: 2 * time.Second,
|
||||
},
|
||||
)
|
||||
if err == nil {
|
||||
t.Errorf("expected error: %v", err)
|
||||
}
|
||||
if a != nil {
|
||||
t.Fatalf("expected client to be nil on failure")
|
||||
}
|
||||
|
||||
_, err = NewClient(
|
||||
context.Background(),
|
||||
[]string{"http://irrelevant:12345"},
|
||||
ClientOptions{
|
||||
Cert: "blank.crt",
|
||||
Key: "blank.key",
|
||||
CACert: "blank.CACert",
|
||||
DialTimeout: 2 * time.Second,
|
||||
DialKeepAlive: 2 * time.Second,
|
||||
HeaderTimeoutPerRequest: 2 * time.Second,
|
||||
},
|
||||
)
|
||||
if err == nil {
|
||||
t.Errorf("expected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Mocks of the underlying etcd.KeysAPI interface that is called by the methods we want to test
|
||||
|
||||
// fakeKeysAPI implements etcd.KeysAPI, event and err are channels used to emulate
|
||||
// an etcd event or error, getres will be returned when etcd.KeysAPI.Get is called.
|
||||
type fakeKeysAPI struct {
|
||||
event chan bool
|
||||
err chan bool
|
||||
getres *getResult
|
||||
}
|
||||
|
||||
type getResult struct {
|
||||
resp *etcd.Response
|
||||
err error
|
||||
}
|
||||
|
||||
// Get return the content of getres or nil, nil
|
||||
func (fka *fakeKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
|
||||
if fka.getres == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return fka.getres.resp, fka.getres.err
|
||||
}
|
||||
|
||||
// Set is not used in the tests
|
||||
func (fka *fakeKeysAPI) Set(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Delete is not used in the tests
|
||||
func (fka *fakeKeysAPI) Delete(ctx context.Context, key string, opts *etcd.DeleteOptions) (*etcd.Response, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create is not used in the tests
|
||||
func (fka *fakeKeysAPI) Create(ctx context.Context, key, value string) (*etcd.Response, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CreateInOrder is not used in the tests
|
||||
func (fka *fakeKeysAPI) CreateInOrder(ctx context.Context, dir, value string, opts *etcd.CreateInOrderOptions) (*etcd.Response, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Update is not used in the tests
|
||||
func (fka *fakeKeysAPI) Update(ctx context.Context, key, value string) (*etcd.Response, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Watcher return a fakeWatcher that will forward event and error received on the channels
|
||||
func (fka *fakeKeysAPI) Watcher(key string, opts *etcd.WatcherOptions) etcd.Watcher {
|
||||
return &fakeWatcher{fka.event, fka.err}
|
||||
}
|
||||
|
||||
// fakeWatcher implements etcd.Watcher
|
||||
type fakeWatcher struct {
|
||||
event chan bool
|
||||
err chan bool
|
||||
}
|
||||
|
||||
// Next blocks until an etcd event or error is emulated.
|
||||
// When an event occurs it just return nil response and error.
|
||||
// When an error occur it return a non nil error.
|
||||
func (fw *fakeWatcher) Next(context.Context) (*etcd.Response, error) {
|
||||
for {
|
||||
select {
|
||||
case <-fw.event:
|
||||
return nil, nil
|
||||
case <-fw.err:
|
||||
return nil, errors.New("error from underlying etcd watcher")
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newFakeClient return a new etcd.Client built on top of the mocked interfaces
|
||||
func newFakeClient(event, err chan bool, getres *getResult) Client {
|
||||
return &client{
|
||||
keysAPI: &fakeKeysAPI{event, err, getres},
|
||||
ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// Register should fail when the provided service has an empty key or value
|
||||
func TestRegisterClient(t *testing.T) {
|
||||
client := newFakeClient(nil, nil, nil)
|
||||
|
||||
err := client.Register(Service{Key: "", Value: "value", DeleteOptions: nil})
|
||||
if want, have := ErrNoKey, err; want != have {
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
|
||||
err = client.Register(Service{Key: "key", Value: "", DeleteOptions: nil})
|
||||
if want, have := ErrNoValue, err; want != have {
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
|
||||
err = client.Register(Service{Key: "key", Value: "value", DeleteOptions: nil})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Deregister should fail if the input service has an empty key
|
||||
func TestDeregisterClient(t *testing.T) {
|
||||
client := newFakeClient(nil, nil, nil)
|
||||
|
||||
err := client.Deregister(Service{Key: "", Value: "value", DeleteOptions: nil})
|
||||
if want, have := ErrNoKey, err; want != have {
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
|
||||
err = client.Deregister(Service{Key: "key", Value: "", DeleteOptions: nil})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// WatchPrefix notify the caller by writing on the channel if an etcd event occurs
|
||||
// or return in case of an underlying error
|
||||
func TestWatchPrefix(t *testing.T) {
|
||||
err := make(chan bool)
|
||||
event := make(chan bool)
|
||||
watchPrefixReturned := make(chan bool, 1)
|
||||
client := newFakeClient(event, err, nil)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
client.WatchPrefix("prefix", ch) // block until an etcd event or error occurs
|
||||
watchPrefixReturned <- true
|
||||
}()
|
||||
|
||||
// WatchPrefix force the caller to read once from the channel before actually
|
||||
// sending notification, emulate that first read.
|
||||
<-ch
|
||||
|
||||
// Emulate an etcd event
|
||||
event <- true
|
||||
if want, have := struct{}{}, <-ch; want != have {
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
|
||||
// Emulate an error, WatchPrefix should return
|
||||
err <- true
|
||||
select {
|
||||
case <-watchPrefixReturned:
|
||||
break
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("WatchPrefix not returning on errors")
|
||||
}
|
||||
}
|
||||
|
||||
var errKeyAPI = errors.New("emulate error returned by KeysAPI.Get")
|
||||
|
||||
// table of test cases for method GetEntries
|
||||
var getEntriesTestTable = []struct {
|
||||
input getResult // value returned by the underlying etcd.KeysAPI.Get
|
||||
resp []string // response expected in output of GetEntries
|
||||
err error //error expected in output of GetEntries
|
||||
|
||||
}{
|
||||
// test case: an error is returned by etcd.KeysAPI.Get
|
||||
{getResult{nil, errKeyAPI}, nil, errKeyAPI},
|
||||
// test case: return a single leaf node, with an empty value
|
||||
{getResult{&etcd.Response{
|
||||
Action: "get",
|
||||
Node: &etcd.Node{
|
||||
Key: "nodekey",
|
||||
Dir: false,
|
||||
Value: "",
|
||||
Nodes: nil,
|
||||
CreatedIndex: 0,
|
||||
ModifiedIndex: 0,
|
||||
Expiration: nil,
|
||||
TTL: 0,
|
||||
},
|
||||
PrevNode: nil,
|
||||
Index: 0,
|
||||
}, nil}, []string{}, nil},
|
||||
// test case: return a single leaf node, with a value
|
||||
{getResult{&etcd.Response{
|
||||
Action: "get",
|
||||
Node: &etcd.Node{
|
||||
Key: "nodekey",
|
||||
Dir: false,
|
||||
Value: "nodevalue",
|
||||
Nodes: nil,
|
||||
CreatedIndex: 0,
|
||||
ModifiedIndex: 0,
|
||||
Expiration: nil,
|
||||
TTL: 0,
|
||||
},
|
||||
PrevNode: nil,
|
||||
Index: 0,
|
||||
}, nil}, []string{"nodevalue"}, nil},
|
||||
// test case: return a node with two childs
|
||||
{getResult{&etcd.Response{
|
||||
Action: "get",
|
||||
Node: &etcd.Node{
|
||||
Key: "nodekey",
|
||||
Dir: true,
|
||||
Value: "nodevalue",
|
||||
Nodes: []*etcd.Node{
|
||||
{
|
||||
Key: "childnode1",
|
||||
Dir: false,
|
||||
Value: "childvalue1",
|
||||
Nodes: nil,
|
||||
CreatedIndex: 0,
|
||||
ModifiedIndex: 0,
|
||||
Expiration: nil,
|
||||
TTL: 0,
|
||||
},
|
||||
{
|
||||
Key: "childnode2",
|
||||
Dir: false,
|
||||
Value: "childvalue2",
|
||||
Nodes: nil,
|
||||
CreatedIndex: 0,
|
||||
ModifiedIndex: 0,
|
||||
Expiration: nil,
|
||||
TTL: 0,
|
||||
},
|
||||
},
|
||||
CreatedIndex: 0,
|
||||
ModifiedIndex: 0,
|
||||
Expiration: nil,
|
||||
TTL: 0,
|
||||
},
|
||||
PrevNode: nil,
|
||||
Index: 0,
|
||||
}, nil}, []string{"childvalue1", "childvalue2"}, nil},
|
||||
}
|
||||
|
||||
func TestGetEntries(t *testing.T) {
|
||||
for _, et := range getEntriesTestTable {
|
||||
client := newFakeClient(nil, nil, &et.input)
|
||||
resp, err := client.GetEntries("prefix")
|
||||
if want, have := et.resp, resp; !reflect.DeepEqual(want, have) {
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
if want, have := et.err, err; want != have {
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
}
|
||||
}
|
4
vendor/github.com/go-kit/kit/sd/etcd/doc.go
generated
vendored
Normal file
4
vendor/github.com/go-kit/kit/sd/etcd/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package etcd provides a Subscriber and Registrar implementation for etcd. If
|
||||
// you use etcd as your service discovery system, this package will help you
|
||||
// implement the registration and client-side load balancing patterns.
|
||||
package etcd
|
66
vendor/github.com/go-kit/kit/sd/etcd/example_test.go
generated
vendored
Normal file
66
vendor/github.com/go-kit/kit/sd/etcd/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/sd/lb"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
// Let's say this is a service that means to register itself.
|
||||
// First, we will set up some context.
|
||||
var (
|
||||
etcdServer = "http://10.0.0.1:2379" // don't forget schema and port!
|
||||
prefix = "/services/foosvc/" // known at compile time
|
||||
instance = "1.2.3.4:8080" // taken from runtime or platform, somehow
|
||||
key = prefix + instance // should be globally unique
|
||||
value = "http://" + instance // based on our transport
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
// Build the client.
|
||||
client, err := NewClient(ctx, []string{etcdServer}, ClientOptions{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Build the registrar.
|
||||
registrar := NewRegistrar(client, Service{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}, log.NewNopLogger())
|
||||
|
||||
// Register our instance.
|
||||
registrar.Register()
|
||||
|
||||
// At the end of our service lifecycle, for example at the end of func main,
|
||||
// we should make sure to deregister ourselves. This is important! Don't
|
||||
// accidentally skip this step by invoking a log.Fatal or os.Exit in the
|
||||
// interim, which bypasses the defer stack.
|
||||
defer registrar.Deregister()
|
||||
|
||||
// It's likely that we'll also want to connect to other services and call
|
||||
// their methods. We can build a subscriber to listen for changes from etcd
|
||||
// and build endpoints, wrap it with a load-balancer to pick a single
|
||||
// endpoint, and finally wrap it with a retry strategy to get something that
|
||||
// can be used as an endpoint directly.
|
||||
barPrefix := "/services/barsvc"
|
||||
subscriber, err := NewSubscriber(client, barPrefix, barFactory, log.NewNopLogger())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
balancer := lb.NewRoundRobin(subscriber)
|
||||
retry := lb.Retry(3, 3*time.Second, balancer)
|
||||
|
||||
// And now retry can be used like any other endpoint.
|
||||
req := struct{}{}
|
||||
if _, err = retry(ctx, req); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func barFactory(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil }
|
119
vendor/github.com/go-kit/kit/sd/etcd/integration_test.go
generated
vendored
Normal file
119
vendor/github.com/go-kit/kit/sd/etcd/integration_test.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
// +build integration
|
||||
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/go-kit/kit/log"
|
||||
)
|
||||
|
||||
// Package sd/etcd provides a wrapper around the etcd key/value store. This
|
||||
// example assumes the user has an instance of etcd installed and running
|
||||
// locally on port 2379.
|
||||
func TestIntegration(t *testing.T) {
|
||||
addr := os.Getenv("ETCD_ADDR")
|
||||
if addr == "" {
|
||||
t.Skip("ETCD_ADDR not set; skipping integration test")
|
||||
}
|
||||
|
||||
var (
|
||||
prefix = "/services/foosvc/" // known at compile time
|
||||
instance = "1.2.3.4:8080" // taken from runtime or platform, somehow
|
||||
key = prefix + instance
|
||||
value = "http://" + instance // based on our transport
|
||||
)
|
||||
|
||||
client, err := NewClient(context.Background(), []string{addr}, ClientOptions{
|
||||
DialTimeout: 2 * time.Second,
|
||||
DialKeepAlive: 2 * time.Second,
|
||||
HeaderTimeoutPerRequest: 2 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewClient(%q): %v", addr, err)
|
||||
}
|
||||
|
||||
// Verify test data is initially empty.
|
||||
entries, err := client.GetEntries(key)
|
||||
if err == nil {
|
||||
t.Fatalf("GetEntries(%q): expected error, got none", key)
|
||||
}
|
||||
t.Logf("GetEntries(%q): %v (OK)", key, err)
|
||||
|
||||
// Instantiate a new Registrar, passing in test data.
|
||||
registrar := NewRegistrar(client, Service{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}, log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar"))
|
||||
|
||||
// Register our instance.
|
||||
registrar.Register()
|
||||
t.Logf("Registered")
|
||||
|
||||
// Retrieve entries from etcd manually.
|
||||
entries, err = client.GetEntries(key)
|
||||
if err != nil {
|
||||
t.Fatalf("client.GetEntries(%q): %v", key, err)
|
||||
}
|
||||
if want, have := 1, len(entries); want != have {
|
||||
t.Fatalf("client.GetEntries(%q): want %d, have %d", key, want, have)
|
||||
}
|
||||
if want, have := value, entries[0]; want != have {
|
||||
t.Fatalf("want %q, have %q", want, have)
|
||||
}
|
||||
|
||||
subscriber, err := NewSubscriber(
|
||||
client,
|
||||
prefix,
|
||||
func(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil },
|
||||
log.With(log.NewLogfmtLogger(os.Stderr), "component", "subscriber"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewSubscriber: %v", err)
|
||||
}
|
||||
t.Logf("Constructed Subscriber OK")
|
||||
|
||||
if !within(time.Second, func() bool {
|
||||
endpoints, err := subscriber.Endpoints()
|
||||
return err == nil && len(endpoints) == 1
|
||||
}) {
|
||||
t.Fatalf("Subscriber didn't see Register in time")
|
||||
}
|
||||
t.Logf("Subscriber saw Register OK")
|
||||
|
||||
// Deregister first instance of test data.
|
||||
registrar.Deregister()
|
||||
t.Logf("Deregistered")
|
||||
|
||||
// Check it was deregistered.
|
||||
if !within(time.Second, func() bool {
|
||||
endpoints, err := subscriber.Endpoints()
|
||||
t.Logf("Checking Deregister: len(endpoints) = %d, err = %v", len(endpoints), err)
|
||||
return err == nil && len(endpoints) == 0
|
||||
}) {
|
||||
t.Fatalf("Subscriber didn't see Deregister in time")
|
||||
}
|
||||
|
||||
// Verify test data no longer exists in etcd.
|
||||
_, err = client.GetEntries(key)
|
||||
if err == nil {
|
||||
t.Fatalf("GetEntries(%q): expected error, got none", key)
|
||||
}
|
||||
t.Logf("GetEntries(%q): %v (OK)", key, err)
|
||||
}
|
||||
|
||||
func within(d time.Duration, f func() bool) bool {
|
||||
deadline := time.Now().Add(d)
|
||||
for time.Now().Before(deadline) {
|
||||
if f() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(d / 10)
|
||||
}
|
||||
return false
|
||||
}
|
120
vendor/github.com/go-kit/kit/sd/etcd/registrar.go
generated
vendored
Normal file
120
vendor/github.com/go-kit/kit/sd/etcd/registrar.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
)
|
||||
|
||||
const minHeartBeatTime = 500 * time.Millisecond
|
||||
|
||||
// Registrar registers service instance liveness information to etcd.
|
||||
type Registrar struct {
|
||||
client Client
|
||||
service Service
|
||||
logger log.Logger
|
||||
|
||||
quitmtx sync.Mutex
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// Service holds the instance identifying data you want to publish to etcd. Key
|
||||
// must be unique, and value is the string returned to subscribers, typically
|
||||
// called the "instance" string in other parts of package sd.
|
||||
type Service struct {
|
||||
Key string // unique key, e.g. "/service/foobar/1.2.3.4:8080"
|
||||
Value string // returned to subscribers, e.g. "http://1.2.3.4:8080"
|
||||
TTL *TTLOption
|
||||
DeleteOptions *etcd.DeleteOptions
|
||||
}
|
||||
|
||||
// TTLOption allow setting a key with a TTL. This option will be used by a loop
|
||||
// goroutine which regularly refreshes the lease of the key.
|
||||
type TTLOption struct {
|
||||
heartbeat time.Duration // e.g. time.Second * 3
|
||||
ttl time.Duration // e.g. time.Second * 10
|
||||
}
|
||||
|
||||
// NewTTLOption returns a TTLOption that contains proper TTL settings. Heartbeat
|
||||
// is used to refresh the lease of the key periodically; its value should be at
|
||||
// least 500ms. TTL defines the lease of the key; its value should be
|
||||
// significantly greater than heartbeat.
|
||||
//
|
||||
// Good default values might be 3s heartbeat, 10s TTL.
|
||||
func NewTTLOption(heartbeat, ttl time.Duration) *TTLOption {
|
||||
if heartbeat <= minHeartBeatTime {
|
||||
heartbeat = minHeartBeatTime
|
||||
}
|
||||
if ttl <= heartbeat {
|
||||
ttl = 3 * heartbeat
|
||||
}
|
||||
return &TTLOption{
|
||||
heartbeat: heartbeat,
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegistrar returns a etcd Registrar acting on the provided catalog
|
||||
// registration (service).
|
||||
func NewRegistrar(client Client, service Service, logger log.Logger) *Registrar {
|
||||
return &Registrar{
|
||||
client: client,
|
||||
service: service,
|
||||
logger: log.With(logger, "key", service.Key, "value", service.Value),
|
||||
}
|
||||
}
|
||||
|
||||
// Register implements the sd.Registrar interface. Call it when you want your
|
||||
// service to be registered in etcd, typically at startup.
|
||||
func (r *Registrar) Register() {
|
||||
if err := r.client.Register(r.service); err != nil {
|
||||
r.logger.Log("err", err)
|
||||
} else {
|
||||
r.logger.Log("action", "register")
|
||||
}
|
||||
if r.service.TTL != nil {
|
||||
go r.loop()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registrar) loop() {
|
||||
r.quitmtx.Lock()
|
||||
if r.quit != nil {
|
||||
return // already running
|
||||
}
|
||||
r.quit = make(chan struct{})
|
||||
r.quitmtx.Unlock()
|
||||
|
||||
tick := time.NewTicker(r.service.TTL.heartbeat)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
if err := r.client.Register(r.service); err != nil {
|
||||
r.logger.Log("err", err)
|
||||
}
|
||||
case <-r.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deregister implements the sd.Registrar interface. Call it when you want your
|
||||
// service to be deregistered from etcd, typically just prior to shutdown.
|
||||
func (r *Registrar) Deregister() {
|
||||
if err := r.client.Deregister(r.service); err != nil {
|
||||
r.logger.Log("err", err)
|
||||
} else {
|
||||
r.logger.Log("action", "deregister")
|
||||
}
|
||||
|
||||
r.quitmtx.Lock()
|
||||
defer r.quitmtx.Unlock()
|
||||
if r.quit != nil {
|
||||
close(r.quit)
|
||||
r.quit = nil
|
||||
}
|
||||
}
|
111
vendor/github.com/go-kit/kit/sd/etcd/registrar_test.go
generated
vendored
Normal file
111
vendor/github.com/go-kit/kit/sd/etcd/registrar_test.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
)
|
||||
|
||||
// testClient is a basic implementation of Client
|
||||
type testClient struct {
|
||||
registerRes error // value returned when Register or Deregister is called
|
||||
}
|
||||
|
||||
func (tc *testClient) GetEntries(prefix string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (tc *testClient) WatchPrefix(prefix string, ch chan struct{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (tc *testClient) Register(s Service) error {
|
||||
return tc.registerRes
|
||||
}
|
||||
|
||||
func (tc *testClient) Deregister(s Service) error {
|
||||
return tc.registerRes
|
||||
}
|
||||
|
||||
// default service used to build registrar in our tests
|
||||
var testService = Service{"testKey", "testValue", nil, nil}
|
||||
|
||||
// NewRegistar should return a registar with a logger using the service key and value
|
||||
func TestNewRegistar(t *testing.T) {
|
||||
c := Client(&testClient{nil})
|
||||
buf := &bytes.Buffer{}
|
||||
logger := log.NewLogfmtLogger(buf)
|
||||
r := NewRegistrar(
|
||||
c,
|
||||
testService,
|
||||
logger,
|
||||
)
|
||||
|
||||
if err := r.logger.Log("msg", "message"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := "key=testKey value=testValue msg=message\n", buf.String(); want != have {
|
||||
t.Errorf("\nwant: %shave: %s", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
// Register log the error returned by the client or log the successful registration action
|
||||
// table of test cases for method Register
|
||||
var registerTestTable = []struct {
|
||||
registerRes error // value returned by the client on calls to Register
|
||||
log string // expected log by the registrar
|
||||
|
||||
}{
|
||||
// test case: an error is returned by the client
|
||||
{errors.New("regError"), "key=testKey value=testValue err=regError\n"},
|
||||
// test case: registration successful
|
||||
{nil, "key=testKey value=testValue action=register\n"},
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
for _, tc := range registerTestTable {
|
||||
c := Client(&testClient{tc.registerRes})
|
||||
buf := &bytes.Buffer{}
|
||||
logger := log.NewLogfmtLogger(buf)
|
||||
r := NewRegistrar(
|
||||
c,
|
||||
testService,
|
||||
logger,
|
||||
)
|
||||
r.Register()
|
||||
if want, have := tc.log, buf.String(); want != have {
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deregister log the error returned by the client or log the successful deregistration action
|
||||
// table of test cases for method Deregister
|
||||
var deregisterTestTable = []struct {
|
||||
deregisterRes error // value returned by the client on calls to Deregister
|
||||
log string // expected log by the registrar
|
||||
}{
|
||||
// test case: an error is returned by the client
|
||||
{errors.New("deregError"), "key=testKey value=testValue err=deregError\n"},
|
||||
// test case: deregistration successful
|
||||
{nil, "key=testKey value=testValue action=deregister\n"},
|
||||
}
|
||||
|
||||
func TestDeregister(t *testing.T) {
|
||||
for _, tc := range deregisterTestTable {
|
||||
c := Client(&testClient{tc.deregisterRes})
|
||||
buf := &bytes.Buffer{}
|
||||
logger := log.NewLogfmtLogger(buf)
|
||||
r := NewRegistrar(
|
||||
c,
|
||||
testService,
|
||||
logger,
|
||||
)
|
||||
r.Deregister()
|
||||
if want, have := tc.log, buf.String(); want != have {
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
}
|
||||
}
|
72
vendor/github.com/go-kit/kit/sd/etcd/subscriber.go
generated
vendored
Normal file
72
vendor/github.com/go-kit/kit/sd/etcd/subscriber.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/sd"
|
||||
"github.com/go-kit/kit/sd/cache"
|
||||
)
|
||||
|
||||
// Subscriber yield endpoints stored in a certain etcd keyspace. Any kind of
|
||||
// change in that keyspace is watched and will update the Subscriber endpoints.
|
||||
type Subscriber struct {
|
||||
client Client
|
||||
prefix string
|
||||
cache *cache.Cache
|
||||
logger log.Logger
|
||||
quitc chan struct{}
|
||||
}
|
||||
|
||||
var _ sd.Subscriber = &Subscriber{}
|
||||
|
||||
// NewSubscriber returns an etcd subscriber. It will start watching the given
|
||||
// prefix for changes, and update the endpoints.
|
||||
func NewSubscriber(c Client, prefix string, factory sd.Factory, logger log.Logger) (*Subscriber, error) {
|
||||
s := &Subscriber{
|
||||
client: c,
|
||||
prefix: prefix,
|
||||
cache: cache.New(factory, logger),
|
||||
logger: logger,
|
||||
quitc: make(chan struct{}),
|
||||
}
|
||||
|
||||
instances, err := s.client.GetEntries(s.prefix)
|
||||
if err == nil {
|
||||
logger.Log("prefix", s.prefix, "instances", len(instances))
|
||||
} else {
|
||||
logger.Log("prefix", s.prefix, "err", err)
|
||||
}
|
||||
s.cache.Update(instances)
|
||||
|
||||
go s.loop()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Subscriber) loop() {
|
||||
ch := make(chan struct{})
|
||||
go s.client.WatchPrefix(s.prefix, ch)
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
instances, err := s.client.GetEntries(s.prefix)
|
||||
if err != nil {
|
||||
s.logger.Log("msg", "failed to retrieve entries", "err", err)
|
||||
continue
|
||||
}
|
||||
s.cache.Update(instances)
|
||||
|
||||
case <-s.quitc:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Endpoints implements the Subscriber interface.
|
||||
func (s *Subscriber) Endpoints() ([]endpoint.Endpoint, error) {
|
||||
return s.cache.Endpoints(), nil
|
||||
}
|
||||
|
||||
// Stop terminates the Subscriber.
|
||||
func (s *Subscriber) Stop() {
|
||||
close(s.quitc)
|
||||
}
|
96
vendor/github.com/go-kit/kit/sd/etcd/subscriber_test.go
generated
vendored
Normal file
96
vendor/github.com/go-kit/kit/sd/etcd/subscriber_test.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
stdetcd "github.com/coreos/etcd/client"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/go-kit/kit/log"
|
||||
)
|
||||
|
||||
var (
|
||||
node = &stdetcd.Node{
|
||||
Key: "/foo",
|
||||
Nodes: []*stdetcd.Node{
|
||||
{Key: "/foo/1", Value: "1:1"},
|
||||
{Key: "/foo/2", Value: "1:2"},
|
||||
},
|
||||
}
|
||||
fakeResponse = &stdetcd.Response{
|
||||
Node: node,
|
||||
}
|
||||
)
|
||||
|
||||
func TestSubscriber(t *testing.T) {
|
||||
factory := func(string) (endpoint.Endpoint, io.Closer, error) {
|
||||
return endpoint.Nop, nil, nil
|
||||
}
|
||||
|
||||
client := &fakeClient{
|
||||
responses: map[string]*stdetcd.Response{"/foo": fakeResponse},
|
||||
}
|
||||
|
||||
s, err := NewSubscriber(client, "/foo", factory, log.NewNopLogger())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Stop()
|
||||
|
||||
if _, err := s.Endpoints(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadFactory(t *testing.T) {
|
||||
factory := func(string) (endpoint.Endpoint, io.Closer, error) {
|
||||
return nil, nil, errors.New("kaboom")
|
||||
}
|
||||
|
||||
client := &fakeClient{
|
||||
responses: map[string]*stdetcd.Response{"/foo": fakeResponse},
|
||||
}
|
||||
|
||||
s, err := NewSubscriber(client, "/foo", factory, log.NewNopLogger())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Stop()
|
||||
|
||||
endpoints, err := s.Endpoints()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if want, have := 0, len(endpoints); want != have {
|
||||
t.Errorf("want %d, have %d", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeClient struct {
|
||||
responses map[string]*stdetcd.Response
|
||||
}
|
||||
|
||||
func (c *fakeClient) GetEntries(prefix string) ([]string, error) {
|
||||
response, ok := c.responses[prefix]
|
||||
if !ok {
|
||||
return nil, errors.New("key not exist")
|
||||
}
|
||||
|
||||
entries := make([]string, len(response.Node.Nodes))
|
||||
for i, node := range response.Node.Nodes {
|
||||
entries[i] = node.Value
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) WatchPrefix(prefix string, ch chan struct{}) {}
|
||||
|
||||
func (c *fakeClient) Register(Service) error {
|
||||
return nil
|
||||
}
|
||||
func (c *fakeClient) Deregister(Service) error {
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user