Add Eureka service discovery implementation
This commit adds a service discovery implementation for the Eureka
registry, a component in Netflix's OSS suite. Eureka is a popular
choice in JVM-based microservice architectures, particularly when used
in conjunction with the Spring Cloud ecosystem.
This implementation delegates integration to Fargo: the de facto Golang
Eureka client. It allows the user to employ a Fargo connection as the
foundational configuration item for Registrars and Subscribers. This
should offer the user the most control of Fargo within the constraints
of the Go-kit service discovery abstractions.
Martin Baillie
6 years ago
23 | 23 | ETCD_ADDR: http://localhost:2379 |
24 | 24 | CONSUL_ADDR: localhost:8500 |
25 | 25 | ZK_ADDR: localhost:2181 |
26 | EUREKA_ADDR: http://localhost:8761/eureka |
13 | 13 | image: zookeeper |
14 | 14 | ports: |
15 | 15 | - "2181:2181" |
16 | eureka: | |
17 | image: springcloud/eureka | |
18 | environment: | |
19 | eureka.server.responseCacheUpdateIntervalMs: 1000 | |
20 | ports: | |
21 | - "8761:8761" |
0 | package eureka | |
1 | ||
2 | import ( | |
3 | stdeureka "github.com/hudl/fargo" | |
4 | stdeurekalogging "github.com/op/go-logging" | |
5 | ) | |
6 | ||
7 | func init() { | |
8 | // Quieten Fargo's own logging | |
9 | stdeurekalogging.SetLevel(stdeurekalogging.ERROR, "fargo") | |
10 | } | |
11 | ||
12 | // Client is a wrapper around the Eureka API. | |
13 | type Client interface { | |
14 | // Register an instance with Eureka. | |
15 | Register(i *stdeureka.Instance) error | |
16 | ||
17 | // Deregister an instance from Eureka. | |
18 | Deregister(i *stdeureka.Instance) error | |
19 | ||
20 | // Send an instance heartbeat to Eureka. | |
21 | Heartbeat(i *stdeureka.Instance) error | |
22 | ||
23 | // Get all instances for an app in Eureka. | |
24 | Instances(app string) ([]*stdeureka.Instance, error) | |
25 | ||
26 | // Receive scheduled updates about an app's instances in Eureka. | |
27 | ScheduleUpdates(app string, quitc chan struct{}) <-chan stdeureka.AppUpdate | |
28 | } | |
29 | ||
30 | type client struct { | |
31 | connection *stdeureka.EurekaConnection | |
32 | } | |
33 | ||
34 | // NewClient returns an implementation of the Client interface, wrapping a | |
35 | // concrete connection to Eureka using the Fargo library. | |
36 | // Taking in Fargo's own connection abstraction gives the user maximum | |
37 | // freedom in regards to how that connection is configured. | |
38 | func NewClient(ec *stdeureka.EurekaConnection) Client { | |
39 | return &client{connection: ec} | |
40 | } | |
41 | ||
42 | func (c *client) Register(i *stdeureka.Instance) error { | |
43 | if c.instanceRegistered(i) { | |
44 | // Already registered. Send a heartbeat instead. | |
45 | return c.Heartbeat(i) | |
46 | } | |
47 | return c.connection.RegisterInstance(i) | |
48 | } | |
49 | ||
50 | func (c *client) Deregister(i *stdeureka.Instance) error { | |
51 | return c.connection.DeregisterInstance(i) | |
52 | } | |
53 | ||
54 | func (c *client) Heartbeat(i *stdeureka.Instance) (err error) { | |
55 | if err = c.connection.HeartBeatInstance(i); err != nil && c.instanceNotFoundErr(err) { | |
56 | // Instance not registered. Register first before sending heartbeats. | |
57 | return c.Register(i) | |
58 | } | |
59 | return err | |
60 | } | |
61 | ||
62 | func (c *client) Instances(app string) ([]*stdeureka.Instance, error) { | |
63 | stdApp, err := c.connection.GetApp(app) | |
64 | if err != nil { | |
65 | return nil, err | |
66 | } | |
67 | return stdApp.Instances, nil | |
68 | } | |
69 | ||
70 | func (c *client) ScheduleUpdates(app string, quitc chan struct{}) <-chan stdeureka.AppUpdate { | |
71 | return c.connection.ScheduleAppUpdates(app, false, quitc) | |
72 | } | |
73 | ||
74 | func (c *client) instanceRegistered(i *stdeureka.Instance) bool { | |
75 | _, err := c.connection.GetInstance(i.App, i.Id()) | |
76 | return err == nil | |
77 | } | |
78 | ||
79 | func (c *client) instanceNotFoundErr(err error) bool { | |
80 | code, ok := stdeureka.HTTPResponseStatusCode(err) | |
81 | return ok && code == 404 | |
82 | } |
0 | package eureka | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "reflect" | |
5 | ||
6 | "github.com/go-kit/kit/log" | |
7 | stdeureka "github.com/hudl/fargo" | |
8 | ) | |
9 | ||
10 | var ( | |
11 | errTest = errors.New("kaboom") | |
12 | loggerTest = log.NewNopLogger() | |
13 | instanceTest1 = &stdeureka.Instance{ | |
14 | HostName: "server1.acme.org", | |
15 | Port: 8080, | |
16 | App: "go-kit", | |
17 | IPAddr: "192.168.0.1", | |
18 | VipAddress: "192.168.0.1", | |
19 | SecureVipAddress: "192.168.0.1", | |
20 | HealthCheckUrl: "http://server1.acme.org:8080/healthz", | |
21 | StatusPageUrl: "http://server1.acme.org:8080/status", | |
22 | HomePageUrl: "http://server1.acme.org:8080/", | |
23 | Status: stdeureka.UP, | |
24 | DataCenterInfo: stdeureka.DataCenterInfo{Name: stdeureka.MyOwn}, | |
25 | LeaseInfo: stdeureka.LeaseInfo{RenewalIntervalInSecs: 1}, | |
26 | } | |
27 | instanceTest2 = &stdeureka.Instance{ | |
28 | HostName: "server2.acme.org", | |
29 | Port: 8080, | |
30 | App: "go-kit", | |
31 | IPAddr: "192.168.0.2", | |
32 | VipAddress: "192.168.0.2", | |
33 | SecureVipAddress: "192.168.0.2", | |
34 | HealthCheckUrl: "http://server2.acme.org:8080/healthz", | |
35 | StatusPageUrl: "http://server2.acme.org:8080/status", | |
36 | HomePageUrl: "http://server2.acme.org:8080/", | |
37 | Status: stdeureka.UP, | |
38 | DataCenterInfo: stdeureka.DataCenterInfo{Name: stdeureka.MyOwn}, | |
39 | } | |
40 | applicationTest = &stdeureka.Application{ | |
41 | Name: "go-kit", | |
42 | Instances: []*stdeureka.Instance{instanceTest1, instanceTest2}, | |
43 | } | |
44 | ) | |
45 | ||
46 | type testClient struct { | |
47 | instances []*stdeureka.Instance | |
48 | application *stdeureka.Application | |
49 | errInstances error | |
50 | errApplication error | |
51 | errHeartbeat error | |
52 | } | |
53 | ||
54 | func (c *testClient) Register(i *stdeureka.Instance) error { | |
55 | for _, instance := range c.instances { | |
56 | if reflect.DeepEqual(*instance, *i) { | |
57 | return errors.New("already registered") | |
58 | } | |
59 | } | |
60 | ||
61 | c.instances = append(c.instances, i) | |
62 | return nil | |
63 | } | |
64 | ||
65 | func (c *testClient) Deregister(i *stdeureka.Instance) error { | |
66 | var newInstances []*stdeureka.Instance | |
67 | for _, instance := range c.instances { | |
68 | if reflect.DeepEqual(*instance, *i) { | |
69 | continue | |
70 | } | |
71 | newInstances = append(newInstances, instance) | |
72 | } | |
73 | if len(newInstances) == len(c.instances) { | |
74 | return errors.New("not registered") | |
75 | } | |
76 | ||
77 | c.instances = newInstances | |
78 | return nil | |
79 | } | |
80 | ||
81 | func (c *testClient) Heartbeat(i *stdeureka.Instance) (err error) { | |
82 | return c.errHeartbeat | |
83 | } | |
84 | ||
85 | func (c *testClient) Instances(app string) ([]*stdeureka.Instance, error) { | |
86 | return c.instances, c.errInstances | |
87 | } | |
88 | ||
89 | func (c *testClient) ScheduleUpdates(service string, quitc chan struct{}) <-chan stdeureka.AppUpdate { | |
90 | updatec := make(chan stdeureka.AppUpdate, 1) | |
91 | updatec <- stdeureka.AppUpdate{App: c.application, Err: c.errApplication} | |
92 | return updatec | |
93 | } |
0 | // Package eureka provides subscriber and registrar implementations for Netflix OSS's Eureka | |
1 | package eureka |
0 | // +build integration | |
1 | ||
2 | package eureka | |
3 | ||
4 | import ( | |
5 | "io" | |
6 | "os" | |
7 | "testing" | |
8 | "time" | |
9 | ||
10 | "github.com/go-kit/kit/endpoint" | |
11 | "github.com/go-kit/kit/log" | |
12 | stdeureka "github.com/hudl/fargo" | |
13 | ) | |
14 | ||
15 | // Package sd/eureka provides a wrapper around the Netflix Eureka service | |
16 | // registry by way of the Fargo library. This test assumes the user has an | |
17 | // instance of Eureka available at the address in the environment variable. | |
18 | // Example `${EUREKA_ADDR}` format: http://localhost:8761/eureka | |
19 | // | |
20 | // NOTE: when starting a Eureka server for integration testing, ensure | |
21 | // the response cache interval is reduced to one second. This can be | |
22 | // achieved with the following Java argument: | |
23 | // `-Deureka.server.responseCacheUpdateIntervalMs=1000` | |
24 | func TestIntegration(t *testing.T) { | |
25 | eurekaAddr := os.Getenv("EUREKA_ADDR") | |
26 | if eurekaAddr == "" { | |
27 | t.Skip("EUREKA_ADDR is not set") | |
28 | } | |
29 | ||
30 | var client Client | |
31 | { | |
32 | var stdConfig stdeureka.Config | |
33 | stdConfig.Eureka.ServiceUrls = []string{eurekaAddr} | |
34 | stdConfig.Eureka.PollIntervalSeconds = 1 | |
35 | ||
36 | stdConnection := stdeureka.NewConnFromConfig(stdConfig) | |
37 | client = NewClient(&stdConnection) | |
38 | } | |
39 | ||
40 | logger := log.NewLogfmtLogger(os.Stderr) | |
41 | logger = log.With(logger, "ts", log.DefaultTimestamp) | |
42 | ||
43 | // Register one instance. | |
44 | registrar1 := NewRegistrar(client, instanceTest1, log.With(logger, "component", "registrar1")) | |
45 | registrar1.Register() | |
46 | defer registrar1.Deregister() | |
47 | ||
48 | // This should be enough time for the Eureka server response cache to update. | |
49 | time.Sleep(time.Second) | |
50 | ||
51 | // Build a subscriber. | |
52 | factory := func(instance string) (endpoint.Endpoint, io.Closer, error) { | |
53 | t.Logf("factory invoked for %q", instance) | |
54 | return endpoint.Nop, nil, nil | |
55 | } | |
56 | s := NewSubscriber( | |
57 | client, | |
58 | factory, | |
59 | log.With(logger, "component", "subscriber"), | |
60 | instanceTest1.App, | |
61 | ) | |
62 | defer s.Stop() | |
63 | ||
64 | // We should have one endpoint immediately after subscriber instantiation. | |
65 | endpoints, err := s.Endpoints() | |
66 | if err != nil { | |
67 | t.Error(err) | |
68 | } | |
69 | if want, have := 1, len(endpoints); want != have { | |
70 | t.Errorf("want %d, have %d", want, have) | |
71 | } | |
72 | ||
73 | // Register a second instance | |
74 | registrar2 := NewRegistrar(client, instanceTest2, log.With(logger, "component", "registrar2")) | |
75 | registrar2.Register() | |
76 | defer registrar2.Deregister() // In case of exceptional circumstances. | |
77 | ||
78 | // This should be enough time for a scheduled update assuming Eureka is | |
79 | // configured with the properties mentioned in the function comments. | |
80 | time.Sleep(2 * time.Second) | |
81 | ||
82 | // Now we should have two endpoints. | |
83 | endpoints, err = s.Endpoints() | |
84 | if err != nil { | |
85 | t.Error(err) | |
86 | } | |
87 | if want, have := 2, len(endpoints); want != have { | |
88 | t.Errorf("want %d, have %d", want, have) | |
89 | } | |
90 | ||
91 | // Deregister the second instance. | |
92 | registrar2.Deregister() | |
93 | ||
94 | // Wait for another scheduled update. | |
95 | time.Sleep(2 * time.Second) | |
96 | ||
97 | // And then there was one. | |
98 | endpoints, err = s.Endpoints() | |
99 | if err != nil { | |
100 | t.Error(err) | |
101 | } | |
102 | if want, have := 1, len(endpoints); want != have { | |
103 | t.Errorf("want %d, have %d", want, have) | |
104 | } | |
105 | } |
0 | package eureka | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "sync" | |
5 | "time" | |
6 | ||
7 | stdeureka "github.com/hudl/fargo" | |
8 | ||
9 | "github.com/go-kit/kit/log" | |
10 | ) | |
11 | ||
12 | // Registrar maintains service instance liveness information in Eureka. | |
13 | type Registrar struct { | |
14 | client Client | |
15 | instance *stdeureka.Instance | |
16 | logger log.Logger | |
17 | ||
18 | quitmtx sync.Mutex | |
19 | quit chan bool | |
20 | } | |
21 | ||
22 | // NewRegistrar returns an Eureka Registrar acting on behalf of the provided | |
23 | // Fargo instance. | |
24 | func NewRegistrar(client Client, i *stdeureka.Instance, l log.Logger) *Registrar { | |
25 | return &Registrar{ | |
26 | client: client, | |
27 | instance: i, | |
28 | logger: log.With(l, "service", i.App, "address", fmt.Sprintf("%s:%d", i.IPAddr, i.Port)), | |
29 | } | |
30 | } | |
31 | ||
32 | // Register implements sd.Registrar interface. | |
33 | func (r *Registrar) Register() { | |
34 | if err := r.client.Register(r.instance); err != nil { | |
35 | r.logger.Log("err", err) | |
36 | } else { | |
37 | r.logger.Log("action", "register") | |
38 | } | |
39 | ||
40 | if r.instance.LeaseInfo.RenewalIntervalInSecs > 0 { | |
41 | // User has opted for heartbeat functionality in Eureka. | |
42 | go r.loop() | |
43 | } | |
44 | } | |
45 | ||
46 | // Deregister implements sd.Registrar interface. | |
47 | func (r *Registrar) Deregister() { | |
48 | if err := r.client.Deregister(r.instance); err != nil { | |
49 | r.logger.Log("err", err) | |
50 | } else { | |
51 | r.logger.Log("action", "deregister") | |
52 | } | |
53 | ||
54 | r.quitmtx.Lock() | |
55 | defer r.quitmtx.Unlock() | |
56 | if r.quit != nil { | |
57 | r.quit <- true | |
58 | } | |
59 | } | |
60 | ||
61 | func (r *Registrar) loop() { | |
62 | r.quitmtx.Lock() | |
63 | if r.quit != nil { | |
64 | defer r.quitmtx.Unlock() | |
65 | return // Already running. | |
66 | } | |
67 | r.quit = make(chan bool) | |
68 | r.quitmtx.Unlock() | |
69 | ||
70 | tick := time.NewTicker(time.Duration(r.instance.LeaseInfo.RenewalIntervalInSecs) * time.Second) | |
71 | defer tick.Stop() | |
72 | for { | |
73 | select { | |
74 | case <-tick.C: | |
75 | if err := r.client.Heartbeat(r.instance); err != nil { | |
76 | r.logger.Log("err", err) | |
77 | } | |
78 | case <-r.quit: | |
79 | r.quitmtx.Lock() | |
80 | defer r.quitmtx.Unlock() | |
81 | ||
82 | close(r.quit) | |
83 | r.quit = nil | |
84 | ||
85 | return | |
86 | } | |
87 | } | |
88 | } |
0 | package eureka | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ||
6 | stdeureka "github.com/hudl/fargo" | |
7 | ) | |
8 | ||
9 | func TestRegistrar(t *testing.T) { | |
10 | client := &testClient{ | |
11 | instances: []*stdeureka.Instance{}, | |
12 | errHeartbeat: errTest, | |
13 | } | |
14 | ||
15 | r := NewRegistrar(client, instanceTest1, loggerTest) | |
16 | if want, have := 0, len(client.instances); want != have { | |
17 | t.Errorf("want %d, have %d", want, have) | |
18 | } | |
19 | ||
20 | // Not registered. | |
21 | r.Deregister() | |
22 | if want, have := 0, len(client.instances); want != have { | |
23 | t.Errorf("want %d, have %d", want, have) | |
24 | } | |
25 | ||
26 | // Register. | |
27 | r.Register() | |
28 | if want, have := 1, len(client.instances); want != have { | |
29 | t.Errorf("want %d, have %d", want, have) | |
30 | } | |
31 | ||
32 | // Deregister. | |
33 | r.Deregister() | |
34 | if want, have := 0, len(client.instances); want != have { | |
35 | t.Errorf("want %d, have %d", want, have) | |
36 | } | |
37 | ||
38 | // Already registered. | |
39 | r.Register() | |
40 | if want, have := 1, len(client.instances); want != have { | |
41 | t.Errorf("want %d, have %d", want, have) | |
42 | } | |
43 | r.Register() | |
44 | if want, have := 1, len(client.instances); want != have { | |
45 | t.Errorf("want %d, have %d", want, have) | |
46 | } | |
47 | ||
48 | // Wait for a heartbeat failure. | |
49 | time.Sleep(time.Second) | |
50 | if want, have := 1, len(client.instances); want != have { | |
51 | t.Errorf("want %d, have %d", want, have) | |
52 | } | |
53 | r.Deregister() | |
54 | if want, have := 0, len(client.instances); want != have { | |
55 | t.Errorf("want %d, have %d", want, have) | |
56 | } | |
57 | } |
0 | package eureka | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ||
5 | stdeureka "github.com/hudl/fargo" | |
6 | ||
7 | "github.com/go-kit/kit/endpoint" | |
8 | "github.com/go-kit/kit/log" | |
9 | "github.com/go-kit/kit/sd" | |
10 | "github.com/go-kit/kit/sd/cache" | |
11 | ) | |
12 | ||
13 | // Subscriber yields endpoints stored in the Eureka registry for the given app. | |
14 | // Changes in that app are watched and will update the Subscriber endpoints. | |
15 | type Subscriber struct { | |
16 | client Client | |
17 | cache *cache.Cache | |
18 | logger log.Logger | |
19 | app string | |
20 | quitc chan struct{} | |
21 | } | |
22 | ||
23 | var _ sd.Subscriber = &Subscriber{} | |
24 | ||
25 | // NewSubscriber returns a Eureka subscriber. It will start watching the given | |
26 | // app string for changes, and update the endpoints accordingly. | |
27 | func NewSubscriber(c Client, f sd.Factory, l log.Logger, app string) *Subscriber { | |
28 | s := &Subscriber{ | |
29 | client: c, | |
30 | cache: cache.New(f, l), | |
31 | app: app, | |
32 | logger: l, | |
33 | quitc: make(chan struct{}), | |
34 | } | |
35 | ||
36 | instances, err := s.getInstances() | |
37 | if err == nil { | |
38 | s.logger.Log("app", s.app, "instances", len(instances)) | |
39 | } else { | |
40 | s.logger.Log("app", s.app, "msg", "failed to retrieve instances", "err", err) | |
41 | } | |
42 | ||
43 | s.cache.Update(instances) | |
44 | go s.loop() | |
45 | return s | |
46 | } | |
47 | ||
48 | func (s *Subscriber) getInstances() ([]string, error) { | |
49 | stdInstances, err := s.client.Instances(s.app) | |
50 | if err != nil { | |
51 | return nil, err | |
52 | } | |
53 | return convertStdInstances(stdInstances), nil | |
54 | } | |
55 | ||
56 | func (s *Subscriber) loop() { | |
57 | updatec := s.client.ScheduleUpdates(s.app, s.quitc) | |
58 | for { | |
59 | select { | |
60 | case <-s.quitc: | |
61 | return | |
62 | case u := <-updatec: | |
63 | if u.Err != nil { | |
64 | s.logger.Log("app", s.app, "msg", "failed to retrieve instances", "err", u.Err) | |
65 | continue | |
66 | } | |
67 | ||
68 | instances := convertStdApplication(u.App) | |
69 | s.logger.Log("app", s.app, "instances", len(instances)) | |
70 | s.cache.Update(instances) | |
71 | } | |
72 | } | |
73 | } | |
74 | ||
75 | // Endpoints implements the Subscriber interface. | |
76 | func (s *Subscriber) Endpoints() ([]endpoint.Endpoint, error) { | |
77 | return s.cache.Endpoints(), nil | |
78 | } | |
79 | ||
80 | // Stop terminates the Subscriber. | |
81 | func (s *Subscriber) Stop() { | |
82 | close(s.quitc) | |
83 | } | |
84 | ||
85 | func convertStdApplication(stdApplication *stdeureka.Application) (instances []string) { | |
86 | if stdApplication != nil { | |
87 | instances = convertStdInstances(stdApplication.Instances) | |
88 | } | |
89 | return instances | |
90 | } | |
91 | ||
92 | func convertStdInstances(stdInstances []*stdeureka.Instance) []string { | |
93 | instances := make([]string, len(stdInstances)) | |
94 | for i, stdInstance := range stdInstances { | |
95 | instances[i] = fmt.Sprintf("%s:%d", stdInstance.IPAddr, stdInstance.Port) | |
96 | } | |
97 | return instances | |
98 | } |
0 | package eureka | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | "testing" | |
5 | "time" | |
6 | ||
7 | "github.com/go-kit/kit/endpoint" | |
8 | stdeureka "github.com/hudl/fargo" | |
9 | ) | |
10 | ||
11 | func TestSubscriber(t *testing.T) { | |
12 | factory := func(string) (endpoint.Endpoint, io.Closer, error) { | |
13 | return endpoint.Nop, nil, nil | |
14 | } | |
15 | ||
16 | client := &testClient{ | |
17 | instances: []*stdeureka.Instance{instanceTest1}, | |
18 | application: applicationTest, | |
19 | errApplication: nil, | |
20 | } | |
21 | ||
22 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
23 | defer s.Stop() | |
24 | ||
25 | endpoints, err := s.Endpoints() | |
26 | if err != nil { | |
27 | t.Fatal(err) | |
28 | } | |
29 | ||
30 | if want, have := 1, len(endpoints); want != have { | |
31 | t.Errorf("want %d, have %d", want, have) | |
32 | } | |
33 | } | |
34 | ||
35 | func TestSubscriberScheduleUpdates(t *testing.T) { | |
36 | factory := func(string) (endpoint.Endpoint, io.Closer, error) { | |
37 | return endpoint.Nop, nil, nil | |
38 | } | |
39 | ||
40 | client := &testClient{ | |
41 | instances: []*stdeureka.Instance{instanceTest1}, | |
42 | application: applicationTest, | |
43 | errApplication: nil, | |
44 | } | |
45 | ||
46 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
47 | defer s.Stop() | |
48 | ||
49 | endpoints, _ := s.Endpoints() | |
50 | if want, have := 1, len(endpoints); want != have { | |
51 | t.Errorf("want %d, have %d", want, have) | |
52 | } | |
53 | ||
54 | time.Sleep(50 * time.Millisecond) | |
55 | ||
56 | endpoints, _ = s.Endpoints() | |
57 | if want, have := 2, len(endpoints); want != have { | |
58 | t.Errorf("want %v, have %v", want, have) | |
59 | } | |
60 | } | |
61 | ||
62 | func TestBadFactory(t *testing.T) { | |
63 | factory := func(string) (endpoint.Endpoint, io.Closer, error) { | |
64 | return nil, nil, errTest | |
65 | } | |
66 | ||
67 | client := &testClient{ | |
68 | instances: []*stdeureka.Instance{instanceTest1}, | |
69 | } | |
70 | ||
71 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
72 | defer s.Stop() | |
73 | ||
74 | endpoints, err := s.Endpoints() | |
75 | if err != nil { | |
76 | t.Fatal(err) | |
77 | } | |
78 | ||
79 | if want, have := 0, len(endpoints); want != have { | |
80 | t.Errorf("want %d, have %d", want, have) | |
81 | } | |
82 | } | |
83 | ||
84 | func TestBadSubscriberInstances(t *testing.T) { | |
85 | factory := func(string) (endpoint.Endpoint, io.Closer, error) { | |
86 | return endpoint.Nop, nil, nil | |
87 | } | |
88 | ||
89 | client := &testClient{ | |
90 | errInstances: errTest, | |
91 | application: applicationTest, | |
92 | errApplication: nil, | |
93 | } | |
94 | ||
95 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
96 | defer s.Stop() | |
97 | ||
98 | endpoints, err := s.Endpoints() | |
99 | if err != nil { | |
100 | t.Fatal(err) | |
101 | } | |
102 | ||
103 | if want, have := 0, len(endpoints); want != have { | |
104 | t.Errorf("want %d, have %d", want, have) | |
105 | } | |
106 | } | |
107 | ||
108 | func TestBadSubscriberScheduleUpdates(t *testing.T) { | |
109 | factory := func(string) (endpoint.Endpoint, io.Closer, error) { | |
110 | return endpoint.Nop, nil, nil | |
111 | } | |
112 | ||
113 | client := &testClient{ | |
114 | instances: []*stdeureka.Instance{instanceTest1}, | |
115 | application: applicationTest, | |
116 | errApplication: errTest, | |
117 | } | |
118 | ||
119 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
120 | defer s.Stop() | |
121 | ||
122 | endpoints, err := s.Endpoints() | |
123 | if err != nil { | |
124 | t.Error(err) | |
125 | } | |
126 | if want, have := 1, len(endpoints); want != have { | |
127 | t.Errorf("want %d, have %d", want, have) | |
128 | } | |
129 | ||
130 | time.Sleep(50 * time.Millisecond) | |
131 | ||
132 | endpoints, err = s.Endpoints() | |
133 | if err != nil { | |
134 | t.Error(err) | |
135 | } | |
136 | if want, have := 1, len(endpoints); want != have { | |
137 | t.Errorf("want %v, have %v", want, have) | |
138 | } | |
139 | } |