Moved to google.golang.org/genproto/googleapis/api/annotations

Fixes #52
This commit is contained in:
Valerio Gheri
2017-03-31 18:01:58 +02:00
parent 024c5a4e4e
commit c40779224f
2037 changed files with 831329 additions and 1854 deletions

177
vendor/github.com/go-kit/kit/sd/etcd/client.go generated vendored Normal file
View 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
View 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
View 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
View 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 }

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

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