Refactoring without client intermediary
Martin Baillie
6 years ago
0 | package eureka | |
1 | ||
2 | import ( | |
3 | "github.com/hudl/fargo" | |
4 | ) | |
5 | ||
6 | // Client is a wrapper around the Eureka API. | |
7 | type Client interface { | |
8 | // Register an instance with Eureka. | |
9 | Register(i *fargo.Instance) error | |
10 | ||
11 | // Deregister an instance from Eureka. | |
12 | Deregister(i *fargo.Instance) error | |
13 | ||
14 | // Send an instance heartbeat to Eureka. | |
15 | Heartbeat(i *fargo.Instance) error | |
16 | ||
17 | // Get all instances for an app in Eureka. | |
18 | Instances(app string) ([]*fargo.Instance, error) | |
19 | ||
20 | // Receive scheduled updates about an app's instances in Eureka. | |
21 | ScheduleUpdates(app string, quitc chan struct{}) <-chan fargo.AppUpdate | |
22 | } | |
23 | ||
24 | type client struct { | |
25 | connection *fargo.EurekaConnection | |
26 | } | |
27 | ||
28 | // NewClient returns an implementation of the Client interface, wrapping a | |
29 | // concrete connection to Eureka using the Fargo library. | |
30 | // Taking in Fargo's own connection abstraction gives the user maximum | |
31 | // freedom in regards to how that connection is configured. | |
32 | func NewClient(ec *fargo.EurekaConnection) Client { | |
33 | return &client{connection: ec} | |
34 | } | |
35 | ||
36 | func (c *client) Register(i *fargo.Instance) error { | |
37 | if c.instanceRegistered(i) { | |
38 | // Already registered. Send a heartbeat instead. | |
39 | return c.Heartbeat(i) | |
40 | } | |
41 | return c.connection.RegisterInstance(i) | |
42 | } | |
43 | ||
44 | func (c *client) Deregister(i *fargo.Instance) error { | |
45 | return c.connection.DeregisterInstance(i) | |
46 | } | |
47 | ||
48 | func (c *client) Heartbeat(i *fargo.Instance) (err error) { | |
49 | if err = c.connection.HeartBeatInstance(i); err != nil && c.instanceNotFoundErr(err) { | |
50 | // Instance not registered. Register first before sending heartbeats. | |
51 | return c.Register(i) | |
52 | } | |
53 | return err | |
54 | } | |
55 | ||
56 | func (c *client) Instances(app string) ([]*fargo.Instance, error) { | |
57 | stdApp, err := c.connection.GetApp(app) | |
58 | if err != nil { | |
59 | return nil, err | |
60 | } | |
61 | return stdApp.Instances, nil | |
62 | } | |
63 | ||
64 | func (c *client) ScheduleUpdates(app string, quitc chan struct{}) <-chan fargo.AppUpdate { | |
65 | return c.connection.ScheduleAppUpdates(app, false, quitc) | |
66 | } | |
67 | ||
68 | func (c *client) instanceRegistered(i *fargo.Instance) bool { | |
69 | _, err := c.connection.GetInstance(i.App, i.Id()) | |
70 | return err == nil | |
71 | } | |
72 | ||
73 | func (c *client) instanceNotFoundErr(err error) bool { | |
74 | code, ok := fargo.HTTPResponseStatusCode(err) | |
75 | return ok && code == 404 | |
76 | } |
0 | package eureka | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "reflect" | |
5 | ||
6 | "github.com/hudl/fargo" | |
7 | ||
8 | "github.com/go-kit/kit/log" | |
9 | ) | |
10 | ||
11 | var ( | |
12 | errTest = errors.New("kaboom") | |
13 | loggerTest = log.NewNopLogger() | |
14 | instanceTest1 = &fargo.Instance{ | |
15 | HostName: "server1.acme.org", | |
16 | Port: 8080, | |
17 | App: "go-kit", | |
18 | IPAddr: "192.168.0.1", | |
19 | VipAddress: "192.168.0.1", | |
20 | SecureVipAddress: "192.168.0.1", | |
21 | HealthCheckUrl: "http://server1.acme.org:8080/healthz", | |
22 | StatusPageUrl: "http://server1.acme.org:8080/status", | |
23 | HomePageUrl: "http://server1.acme.org:8080/", | |
24 | Status: fargo.UP, | |
25 | DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, | |
26 | LeaseInfo: fargo.LeaseInfo{RenewalIntervalInSecs: 1}, | |
27 | } | |
28 | instanceTest2 = &fargo.Instance{ | |
29 | HostName: "server2.acme.org", | |
30 | Port: 8080, | |
31 | App: "go-kit", | |
32 | IPAddr: "192.168.0.2", | |
33 | VipAddress: "192.168.0.2", | |
34 | SecureVipAddress: "192.168.0.2", | |
35 | HealthCheckUrl: "http://server2.acme.org:8080/healthz", | |
36 | StatusPageUrl: "http://server2.acme.org:8080/status", | |
37 | HomePageUrl: "http://server2.acme.org:8080/", | |
38 | Status: fargo.UP, | |
39 | DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, | |
40 | } | |
41 | applicationTest = &fargo.Application{ | |
42 | Name: "go-kit", | |
43 | Instances: []*fargo.Instance{instanceTest1, instanceTest2}, | |
44 | } | |
45 | ) | |
46 | ||
47 | type testClient struct { | |
48 | instances []*fargo.Instance | |
49 | application *fargo.Application | |
50 | errInstances error | |
51 | errApplication error | |
52 | errHeartbeat error | |
53 | } | |
54 | ||
55 | func (c *testClient) Register(i *fargo.Instance) error { | |
56 | for _, instance := range c.instances { | |
57 | if reflect.DeepEqual(*instance, *i) { | |
58 | return errors.New("already registered") | |
59 | } | |
60 | } | |
61 | ||
62 | c.instances = append(c.instances, i) | |
63 | return nil | |
64 | } | |
65 | ||
66 | func (c *testClient) Deregister(i *fargo.Instance) error { | |
67 | var newInstances []*fargo.Instance | |
68 | for _, instance := range c.instances { | |
69 | if reflect.DeepEqual(*instance, *i) { | |
70 | continue | |
71 | } | |
72 | newInstances = append(newInstances, instance) | |
73 | } | |
74 | if len(newInstances) == len(c.instances) { | |
75 | return errors.New("not registered") | |
76 | } | |
77 | ||
78 | c.instances = newInstances | |
79 | return nil | |
80 | } | |
81 | ||
82 | func (c *testClient) Heartbeat(i *fargo.Instance) (err error) { | |
83 | return c.errHeartbeat | |
84 | } | |
85 | ||
86 | func (c *testClient) Instances(app string) ([]*fargo.Instance, error) { | |
87 | return c.instances, c.errInstances | |
88 | } | |
89 | ||
90 | func (c *testClient) ScheduleUpdates(service string, quitc chan struct{}) <-chan fargo.AppUpdate { | |
91 | updatec := make(chan fargo.AppUpdate, 1) | |
92 | updatec <- fargo.AppUpdate{App: c.application, Err: c.errApplication} | |
93 | return updatec | |
94 | } |
28 | 28 | t.Skip("EUREKA_ADDR is not set") |
29 | 29 | } |
30 | 30 | |
31 | var client Client | |
32 | { | |
33 | var fargoConfig fargo.Config | |
34 | fargoConfig.Eureka.ServiceUrls = []string{eurekaAddr} | |
35 | fargoConfig.Eureka.PollIntervalSeconds = 1 | |
36 | ||
37 | fargoConnection := fargo.NewConnFromConfig(fargoConfig) | |
38 | client = NewClient(&fargoConnection) | |
39 | } | |
40 | ||
41 | 31 | logger := log.NewLogfmtLogger(os.Stderr) |
42 | 32 | logger = log.With(logger, "ts", log.DefaultTimestamp) |
43 | 33 | |
34 | var fargoConfig fargo.Config | |
35 | // Target Eureka server(s). | |
36 | fargoConfig.Eureka.ServiceUrls = []string{eurekaAddr} | |
37 | // How often the subscriber should poll for updates. | |
38 | fargoConfig.Eureka.PollIntervalSeconds = 1 | |
39 | ||
40 | // Create a Fargo connection and a Eureka registrar. | |
41 | fargoConnection := fargo.NewConnFromConfig(fargoConfig) | |
42 | registrar1 := NewRegistrar(&fargoConnection, instanceTest1, log.With(logger, "component", "registrar1")) | |
43 | ||
44 | 44 | // Register one instance. |
45 | registrar1 := NewRegistrar(client, instanceTest1, log.With(logger, "component", "registrar1")) | |
46 | 45 | registrar1.Register() |
47 | 46 | defer registrar1.Deregister() |
48 | 47 | |
49 | 48 | // This should be enough time for the Eureka server response cache to update. |
50 | 49 | time.Sleep(time.Second) |
51 | 50 | |
52 | // Build a subscriber. | |
51 | // Build a Eureka subscriber. | |
53 | 52 | factory := func(instance string) (endpoint.Endpoint, io.Closer, error) { |
54 | 53 | t.Logf("factory invoked for %q", instance) |
55 | 54 | return endpoint.Nop, nil, nil |
56 | 55 | } |
57 | 56 | s := NewSubscriber( |
58 | client, | |
57 | &fargoConnection, | |
58 | appNameTest, | |
59 | 59 | factory, |
60 | 60 | log.With(logger, "component", "subscriber"), |
61 | instanceTest1.App, | |
62 | 61 | ) |
63 | 62 | defer s.Stop() |
64 | 63 | |
72 | 71 | } |
73 | 72 | |
74 | 73 | // Register a second instance |
75 | registrar2 := NewRegistrar(client, instanceTest2, log.With(logger, "component", "registrar2")) | |
74 | registrar2 := NewRegistrar(&fargoConnection, instanceTest2, log.With(logger, "component", "registrar2")) | |
76 | 75 | registrar2.Register() |
77 | 76 | defer registrar2.Deregister() // In case of exceptional circumstances. |
78 | 77 |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "fmt" |
4 | "net/http" | |
4 | 5 | "sync" |
5 | 6 | "time" |
6 | 7 | |
7 | 8 | "github.com/hudl/fargo" |
8 | 9 | |
9 | 10 | "github.com/go-kit/kit/log" |
11 | "github.com/go-kit/kit/sd" | |
10 | 12 | ) |
13 | ||
14 | // Matches official Netflix Java client default. | |
15 | const defaultRenewalInterval = 30 * time.Second | |
16 | ||
17 | // The methods of fargo.Connection used in this package. | |
18 | type fargoConnection interface { | |
19 | RegisterInstance(instance *fargo.Instance) error | |
20 | DeregisterInstance(instance *fargo.Instance) error | |
21 | ReregisterInstance(instance *fargo.Instance) error | |
22 | HeartBeatInstance(instance *fargo.Instance) error | |
23 | ScheduleAppUpdates(name string, await bool, done <-chan struct{}) <-chan fargo.AppUpdate | |
24 | GetApp(name string) (*fargo.Application, error) | |
25 | } | |
26 | ||
27 | type fargoUnsuccessfulHTTPResponse struct { | |
28 | statusCode int | |
29 | messagePrefix string | |
30 | } | |
11 | 31 | |
12 | 32 | // Registrar maintains service instance liveness information in Eureka. |
13 | 33 | type Registrar struct { |
14 | client Client | |
34 | conn fargoConnection | |
15 | 35 | instance *fargo.Instance |
16 | 36 | logger log.Logger |
17 | quit chan struct{} | |
18 | wg sync.WaitGroup | |
37 | quitc chan chan struct{} | |
38 | sync.Mutex | |
19 | 39 | } |
20 | 40 | |
41 | var _ sd.Registrar = (*Registrar)(nil) | |
42 | ||
21 | 43 | // NewRegistrar returns an Eureka Registrar acting on behalf of the provided |
22 | // Fargo instance. | |
23 | func NewRegistrar(client Client, i *fargo.Instance, logger log.Logger) *Registrar { | |
44 | // Fargo connection and instance. See the integration test for usage examples. | |
45 | func NewRegistrar(conn fargoConnection, instance *fargo.Instance, logger log.Logger) *Registrar { | |
24 | 46 | return &Registrar{ |
25 | client: client, | |
26 | instance: i, | |
27 | logger: log.With(logger, "service", i.App, "address", fmt.Sprintf("%s:%d", i.IPAddr, i.Port)), | |
47 | conn: conn, | |
48 | instance: instance, | |
49 | logger: log.With(logger, "service", instance.App, "address", fmt.Sprintf("%s:%d", instance.IPAddr, instance.Port)), | |
28 | 50 | } |
29 | 51 | } |
30 | 52 | |
31 | // Register implements sd.Registrar interface. | |
53 | // Register implements sd.Registrar. | |
32 | 54 | func (r *Registrar) Register() { |
33 | if err := r.client.Register(r.instance); err != nil { | |
34 | r.logger.Log("err", err) | |
35 | } else { | |
36 | r.logger.Log("action", "register") | |
55 | r.Lock() | |
56 | defer r.Unlock() | |
57 | ||
58 | if r.quitc != nil { | |
59 | return // Already in the registration loop. | |
37 | 60 | } |
38 | 61 | |
62 | if err := r.conn.RegisterInstance(r.instance); err != nil { | |
63 | r.logger.Log("during", "Register", "err", err) | |
64 | } | |
65 | ||
66 | r.quitc = make(chan chan struct{}) | |
67 | go r.loop() | |
68 | } | |
69 | ||
70 | // Deregister implements sd.Registrar. | |
71 | func (r *Registrar) Deregister() { | |
72 | r.Lock() | |
73 | defer r.Unlock() | |
74 | ||
75 | if r.quitc == nil { | |
76 | return // Already deregistered. | |
77 | } | |
78 | ||
79 | q := make(chan struct{}) | |
80 | r.quitc <- q | |
81 | <-q | |
82 | r.quitc = nil | |
83 | } | |
84 | ||
85 | func (r *Registrar) loop() { | |
86 | var renewalInterval time.Duration | |
39 | 87 | if r.instance.LeaseInfo.RenewalIntervalInSecs > 0 { |
40 | // User has opted for heartbeat functionality in Eureka. | |
41 | if r.quit == nil { | |
42 | r.quit = make(chan struct{}) | |
43 | r.wg.Add(1) | |
44 | go r.loop() | |
88 | renewalInterval = time.Duration(r.instance.LeaseInfo.RenewalIntervalInSecs) * time.Second | |
89 | } else { | |
90 | renewalInterval = defaultRenewalInterval | |
91 | } | |
92 | ticker := time.NewTicker(renewalInterval) | |
93 | defer ticker.Stop() | |
94 | ||
95 | for { | |
96 | select { | |
97 | case <-ticker.C: | |
98 | if err := r.heartbeat(); err != nil { | |
99 | r.logger.Log("during", "heartbeat", "err", err) | |
100 | } | |
101 | ||
102 | case q := <-r.quitc: | |
103 | if err := r.conn.DeregisterInstance(r.instance); err != nil { | |
104 | r.logger.Log("during", "Deregister", "err", err) | |
105 | } | |
106 | close(q) | |
107 | return | |
45 | 108 | } |
46 | 109 | } |
47 | 110 | } |
48 | 111 | |
49 | // Deregister implements sd.Registrar interface. | |
50 | func (r *Registrar) Deregister() { | |
51 | if err := r.client.Deregister(r.instance); err != nil { | |
52 | r.logger.Log("err", err) | |
53 | } else { | |
54 | r.logger.Log("action", "deregister") | |
112 | func (r *Registrar) heartbeat() error { | |
113 | err := r.conn.HeartBeatInstance(r.instance) | |
114 | if err != nil { | |
115 | if u, ok := err.(*fargoUnsuccessfulHTTPResponse); ok && u.statusCode == http.StatusNotFound { | |
116 | // Instance expired (e.g. network partition). Re-register. | |
117 | r.logger.Log("during", "heartbeat", err.Error()) | |
118 | return r.conn.ReregisterInstance(r.instance) | |
119 | } | |
55 | 120 | } |
56 | ||
57 | if r.quit != nil { | |
58 | close(r.quit) | |
59 | r.wg.Wait() | |
60 | r.quit = nil | |
61 | } | |
121 | return err | |
62 | 122 | } |
63 | 123 | |
64 | func (r *Registrar) loop() { | |
65 | tick := time.NewTicker(time.Duration(r.instance.LeaseInfo.RenewalIntervalInSecs) * time.Second) | |
66 | defer tick.Stop() | |
67 | defer r.wg.Done() | |
68 | ||
69 | for { | |
70 | select { | |
71 | case <-tick.C: | |
72 | if err := r.client.Heartbeat(r.instance); err != nil { | |
73 | r.logger.Log("err", err) | |
74 | } | |
75 | case <-r.quit: | |
76 | return | |
77 | } | |
78 | } | |
124 | func (u *fargoUnsuccessfulHTTPResponse) Error() string { | |
125 | return fmt.Sprintf("err=%s code=%d", u.messagePrefix, u.statusCode) | |
79 | 126 | } |
2 | 2 | import ( |
3 | 3 | "testing" |
4 | 4 | "time" |
5 | ||
6 | "github.com/hudl/fargo" | |
7 | 5 | ) |
8 | 6 | |
9 | 7 | func TestRegistrar(t *testing.T) { |
10 | client := &testClient{ | |
11 | instances: []*fargo.Instance{}, | |
8 | connection := &testConnection{ | |
12 | 9 | errHeartbeat: errTest, |
13 | 10 | } |
14 | 11 | |
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 | } | |
12 | registrar1 := NewRegistrar(connection, instanceTest1, loggerTest) | |
13 | registrar2 := NewRegistrar(connection, instanceTest2, loggerTest) | |
19 | 14 | |
20 | 15 | // Not registered. |
21 | r.Deregister() | |
22 | if want, have := 0, len(client.instances); want != have { | |
16 | registrar1.Deregister() | |
17 | if want, have := 0, len(connection.instances); want != have { | |
23 | 18 | t.Errorf("want %d, have %d", want, have) |
24 | 19 | } |
25 | 20 | |
26 | 21 | // Register. |
27 | r.Register() | |
28 | if want, have := 1, len(client.instances); want != have { | |
22 | registrar1.Register() | |
23 | if want, have := 1, len(connection.instances); want != have { | |
24 | t.Errorf("want %d, have %d", want, have) | |
25 | } | |
26 | ||
27 | registrar2.Register() | |
28 | if want, have := 2, len(connection.instances); want != have { | |
29 | 29 | t.Errorf("want %d, have %d", want, have) |
30 | 30 | } |
31 | 31 | |
32 | 32 | // Deregister. |
33 | r.Deregister() | |
34 | if want, have := 0, len(client.instances); want != have { | |
33 | registrar1.Deregister() | |
34 | if want, have := 1, len(connection.instances); want != have { | |
35 | 35 | t.Errorf("want %d, have %d", want, have) |
36 | 36 | } |
37 | 37 | |
38 | 38 | // Already registered. |
39 | r.Register() | |
40 | if want, have := 1, len(client.instances); want != have { | |
39 | registrar1.Register() | |
40 | if want, have := 2, len(connection.instances); want != have { | |
41 | 41 | t.Errorf("want %d, have %d", want, have) |
42 | 42 | } |
43 | r.Register() | |
44 | if want, have := 1, len(client.instances); want != have { | |
43 | registrar1.Register() | |
44 | if want, have := 2, len(connection.instances); want != have { | |
45 | 45 | t.Errorf("want %d, have %d", want, have) |
46 | 46 | } |
47 | 47 | |
48 | 48 | // Wait for a heartbeat failure. |
49 | time.Sleep(time.Second) | |
50 | if want, have := 1, len(client.instances); want != have { | |
49 | time.Sleep(1010 * time.Millisecond) | |
50 | if want, have := 2, len(connection.instances); want != have { | |
51 | 51 | t.Errorf("want %d, have %d", want, have) |
52 | 52 | } |
53 | r.Deregister() | |
54 | if want, have := 0, len(client.instances); want != have { | |
53 | registrar1.Deregister() | |
54 | if want, have := 1, len(connection.instances); want != have { | |
55 | 55 | t.Errorf("want %d, have %d", want, have) |
56 | 56 | } |
57 | 57 | } |
58 | ||
59 | func TestBadRegister(t *testing.T) { | |
60 | connection := &testConnection{ | |
61 | errRegister: errTest, | |
62 | } | |
63 | ||
64 | registrar := NewRegistrar(connection, instanceTest1, loggerTest) | |
65 | registrar.Register() | |
66 | if want, have := 0, len(connection.instances); want != have { | |
67 | t.Errorf("want %d, have %d", want, have) | |
68 | } | |
69 | } | |
70 | ||
71 | func TestBadDeregister(t *testing.T) { | |
72 | connection := &testConnection{ | |
73 | errDeregister: errTest, | |
74 | } | |
75 | ||
76 | registrar := NewRegistrar(connection, instanceTest1, loggerTest) | |
77 | registrar.Register() | |
78 | if want, have := 1, len(connection.instances); want != have { | |
79 | t.Errorf("want %d, have %d", want, have) | |
80 | } | |
81 | registrar.Deregister() | |
82 | if want, have := 1, len(connection.instances); want != have { | |
83 | t.Errorf("want %d, have %d", want, have) | |
84 | } | |
85 | } | |
86 | ||
87 | func TestExpiredInstance(t *testing.T) { | |
88 | connection := &testConnection{ | |
89 | errHeartbeat: errNotFound, | |
90 | } | |
91 | ||
92 | registrar := NewRegistrar(connection, instanceTest1, loggerTest) | |
93 | registrar.Register() | |
94 | ||
95 | // Wait for a heartbeat failure. | |
96 | time.Sleep(1010 * time.Millisecond) | |
97 | ||
98 | if want, have := 1, len(connection.instances); want != have { | |
99 | t.Errorf("want %d, have %d", want, have) | |
100 | } | |
101 | } |
13 | 13 | // Subscriber yields endpoints stored in the Eureka registry for the given app. |
14 | 14 | // Changes in that app are watched and will update the Subscriber endpoints. |
15 | 15 | type Subscriber struct { |
16 | client Client | |
17 | cache *cache.Cache | |
18 | logger log.Logger | |
19 | app string | |
20 | quitc chan struct{} | |
16 | conn fargoConnection | |
17 | app string | |
18 | factory sd.Factory | |
19 | logger log.Logger | |
20 | cache *cache.Cache | |
21 | quitc chan chan struct{} | |
21 | 22 | } |
22 | 23 | |
23 | var _ sd.Subscriber = &Subscriber{} | |
24 | var _ sd.Subscriber = (*Subscriber)(nil) | |
24 | 25 | |
25 | 26 | // NewSubscriber returns a Eureka subscriber. It will start watching the given |
26 | 27 | // app string for changes, and update the endpoints accordingly. |
27 | func NewSubscriber(c Client, factory sd.Factory, logger log.Logger, app string) *Subscriber { | |
28 | func NewSubscriber(conn fargoConnection, app string, factory sd.Factory, logger log.Logger) *Subscriber { | |
29 | logger = log.With(logger, "app", app) | |
30 | ||
28 | 31 | s := &Subscriber{ |
29 | client: c, | |
30 | cache: cache.New(factory, logger), | |
31 | app: app, | |
32 | logger: logger, | |
33 | quitc: make(chan struct{}), | |
32 | conn: conn, | |
33 | app: app, | |
34 | factory: factory, | |
35 | logger: logger, | |
36 | cache: cache.New(factory, logger), | |
37 | quitc: make(chan chan struct{}), | |
34 | 38 | } |
35 | 39 | |
36 | 40 | instances, err := s.getInstances() |
37 | 41 | if err == nil { |
38 | s.logger.Log("app", s.app, "instances", len(instances)) | |
42 | s.logger.Log("instances", len(instances)) | |
39 | 43 | } else { |
40 | s.logger.Log("app", s.app, "msg", "failed to retrieve instances", "err", err) | |
44 | s.logger.Log("during", "getInstances", "err", err) | |
41 | 45 | } |
42 | 46 | |
43 | 47 | s.cache.Update(instances) |
45 | 49 | return s |
46 | 50 | } |
47 | 51 | |
48 | func (s *Subscriber) getInstances() ([]string, error) { | |
49 | fargoInstances, err := s.client.Instances(s.app) | |
50 | if err != nil { | |
51 | return nil, err | |
52 | } | |
53 | return convertFargoInstances(fargoInstances), 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 := convertFargoApplication(u.App) | |
69 | s.logger.Log("app", s.app, "instances", len(instances)) | |
70 | s.cache.Update(instances) | |
71 | } | |
72 | } | |
73 | } | |
74 | ||
75 | 52 | // Endpoints implements the Subscriber interface. |
76 | 53 | func (s *Subscriber) Endpoints() ([]endpoint.Endpoint, error) { |
77 | 54 | return s.cache.Endpoints(), nil |
78 | 55 | } |
79 | 56 | |
80 | // Stop terminates the Subscriber. | |
57 | // Stop terminates the subscriber. | |
81 | 58 | func (s *Subscriber) Stop() { |
82 | close(s.quitc) | |
59 | q := make(chan struct{}) | |
60 | s.quitc <- q | |
61 | <-q | |
62 | s.quitc = nil | |
83 | 63 | } |
84 | 64 | |
85 | func convertFargoApplication(fargoApplication *fargo.Application) (instances []string) { | |
86 | if fargoApplication != nil { | |
87 | instances = convertFargoInstances(fargoApplication.Instances) | |
65 | func (s *Subscriber) loop() { | |
66 | var ( | |
67 | await = false | |
68 | done = make(chan struct{}) | |
69 | updatec = s.conn.ScheduleAppUpdates(s.app, await, done) | |
70 | ) | |
71 | defer close(done) | |
72 | ||
73 | for { | |
74 | select { | |
75 | case update := <-updatec: | |
76 | if update.Err != nil { | |
77 | s.logger.Log("during", "Update", "err", update.Err) | |
78 | continue | |
79 | } | |
80 | instances := convertFargoAppToInstances(update.App) | |
81 | s.logger.Log("instances", len(instances)) | |
82 | s.cache.Update(instances) | |
83 | ||
84 | case q := <-s.quitc: | |
85 | close(q) | |
86 | return | |
87 | } | |
88 | } | |
89 | } | |
90 | ||
91 | func (s *Subscriber) getInstances() ([]string, error) { | |
92 | app, err := s.conn.GetApp(s.app) | |
93 | if err != nil { | |
94 | return nil, err | |
95 | } | |
96 | return convertFargoAppToInstances(app), nil | |
97 | } | |
98 | ||
99 | func convertFargoAppToInstances(app *fargo.Application) []string { | |
100 | instances := make([]string, len(app.Instances)) | |
101 | for i, inst := range app.Instances { | |
102 | instances[i] = fmt.Sprintf("%s:%d", inst.IPAddr, inst.Port) | |
88 | 103 | } |
89 | 104 | return instances |
90 | 105 | } |
91 | ||
92 | func convertFargoInstances(fargoInstances []*fargo.Instance) []string { | |
93 | instances := make([]string, len(fargoInstances)) | |
94 | for i, fargoInstance := range fargoInstances { | |
95 | instances[i] = fmt.Sprintf("%s:%d", fargoInstance.IPAddr, fargoInstance.Port) | |
96 | } | |
97 | return instances | |
98 | } |
14 | 14 | return endpoint.Nop, nil, nil |
15 | 15 | } |
16 | 16 | |
17 | client := &testClient{ | |
17 | connection := &testConnection{ | |
18 | 18 | instances: []*fargo.Instance{instanceTest1}, |
19 | application: applicationTest, | |
19 | application: appUpdateTest, | |
20 | 20 | errApplication: nil, |
21 | 21 | } |
22 | 22 | |
23 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
24 | defer s.Stop() | |
23 | subscriber := NewSubscriber(connection, appNameTest, factory, loggerTest) | |
24 | defer subscriber.Stop() | |
25 | 25 | |
26 | endpoints, err := s.Endpoints() | |
26 | endpoints, err := subscriber.Endpoints() | |
27 | 27 | if err != nil { |
28 | 28 | t.Fatal(err) |
29 | 29 | } |
38 | 38 | return endpoint.Nop, nil, nil |
39 | 39 | } |
40 | 40 | |
41 | client := &testClient{ | |
41 | connection := &testConnection{ | |
42 | 42 | instances: []*fargo.Instance{instanceTest1}, |
43 | application: applicationTest, | |
43 | application: appUpdateTest, | |
44 | 44 | errApplication: nil, |
45 | 45 | } |
46 | 46 | |
47 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
48 | defer s.Stop() | |
47 | subscriber := NewSubscriber(connection, appNameTest, factory, loggerTest) | |
48 | defer subscriber.Stop() | |
49 | 49 | |
50 | endpoints, _ := s.Endpoints() | |
50 | endpoints, _ := subscriber.Endpoints() | |
51 | 51 | if want, have := 1, len(endpoints); want != have { |
52 | 52 | t.Errorf("want %d, have %d", want, have) |
53 | 53 | } |
54 | 54 | |
55 | 55 | time.Sleep(50 * time.Millisecond) |
56 | 56 | |
57 | endpoints, _ = s.Endpoints() | |
57 | endpoints, _ = subscriber.Endpoints() | |
58 | 58 | if want, have := 2, len(endpoints); want != have { |
59 | 59 | t.Errorf("want %v, have %v", want, have) |
60 | 60 | } |
65 | 65 | return nil, nil, errTest |
66 | 66 | } |
67 | 67 | |
68 | client := &testClient{ | |
69 | instances: []*fargo.Instance{instanceTest1}, | |
68 | connection := &testConnection{ | |
69 | instances: []*fargo.Instance{instanceTest1}, | |
70 | application: appUpdateTest, | |
71 | errApplication: nil, | |
70 | 72 | } |
71 | 73 | |
72 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
73 | defer s.Stop() | |
74 | subscriber := NewSubscriber(connection, appNameTest, factory, loggerTest) | |
75 | defer subscriber.Stop() | |
74 | 76 | |
75 | endpoints, err := s.Endpoints() | |
77 | endpoints, err := subscriber.Endpoints() | |
76 | 78 | if err != nil { |
77 | 79 | t.Fatal(err) |
78 | 80 | } |
87 | 89 | return endpoint.Nop, nil, nil |
88 | 90 | } |
89 | 91 | |
90 | client := &testClient{ | |
92 | connection := &testConnection{ | |
93 | instances: []*fargo.Instance{}, | |
91 | 94 | errInstances: errTest, |
92 | application: applicationTest, | |
95 | application: appUpdateTest, | |
93 | 96 | errApplication: nil, |
94 | 97 | } |
95 | 98 | |
96 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
97 | defer s.Stop() | |
99 | subscriber := NewSubscriber(connection, appNameTest, factory, loggerTest) | |
100 | defer subscriber.Stop() | |
98 | 101 | |
99 | endpoints, err := s.Endpoints() | |
102 | endpoints, err := subscriber.Endpoints() | |
100 | 103 | if err != nil { |
101 | 104 | t.Fatal(err) |
102 | 105 | } |
111 | 114 | return endpoint.Nop, nil, nil |
112 | 115 | } |
113 | 116 | |
114 | client := &testClient{ | |
117 | connection := &testConnection{ | |
115 | 118 | instances: []*fargo.Instance{instanceTest1}, |
116 | application: applicationTest, | |
119 | application: appUpdateTest, | |
117 | 120 | errApplication: errTest, |
118 | 121 | } |
119 | 122 | |
120 | s := NewSubscriber(client, factory, loggerTest, instanceTest1.App) | |
121 | defer s.Stop() | |
123 | subscriber := NewSubscriber(connection, appNameTest, factory, loggerTest) | |
124 | defer subscriber.Stop() | |
122 | 125 | |
123 | endpoints, err := s.Endpoints() | |
126 | endpoints, err := subscriber.Endpoints() | |
124 | 127 | if err != nil { |
125 | 128 | t.Error(err) |
126 | 129 | } |
130 | 133 | |
131 | 134 | time.Sleep(50 * time.Millisecond) |
132 | 135 | |
133 | endpoints, err = s.Endpoints() | |
136 | endpoints, err = subscriber.Endpoints() | |
134 | 137 | if err != nil { |
135 | 138 | t.Error(err) |
136 | 139 | } |
0 | package eureka | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "reflect" | |
5 | ||
6 | "github.com/go-kit/kit/log" | |
7 | "github.com/hudl/fargo" | |
8 | ) | |
9 | ||
10 | type testConnection struct { | |
11 | instances []*fargo.Instance | |
12 | application *fargo.Application | |
13 | errInstances error | |
14 | errApplication error | |
15 | errHeartbeat error | |
16 | errRegister error | |
17 | errDeregister error | |
18 | } | |
19 | ||
20 | var ( | |
21 | errTest = errors.New("kaboom") | |
22 | errNotFound = &fargoUnsuccessfulHTTPResponse{statusCode: 404, messagePrefix: "not found"} | |
23 | loggerTest = log.NewNopLogger() | |
24 | appNameTest = "go-kit" | |
25 | appUpdateTest = &fargo.Application{ | |
26 | Name: appNameTest, | |
27 | Instances: []*fargo.Instance{instanceTest1, instanceTest2}, | |
28 | } | |
29 | instanceTest1 = &fargo.Instance{ | |
30 | HostName: "serveregistrar1.acme.org", | |
31 | Port: 8080, | |
32 | App: appNameTest, | |
33 | IPAddr: "192.168.0.1", | |
34 | VipAddress: "192.168.0.1", | |
35 | SecureVipAddress: "192.168.0.1", | |
36 | HealthCheckUrl: "http://serveregistrar1.acme.org:8080/healthz", | |
37 | StatusPageUrl: "http://serveregistrar1.acme.org:8080/status", | |
38 | HomePageUrl: "http://serveregistrar1.acme.org:8080/", | |
39 | Status: fargo.UP, | |
40 | DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, | |
41 | LeaseInfo: fargo.LeaseInfo{RenewalIntervalInSecs: 1}, | |
42 | } | |
43 | instanceTest2 = &fargo.Instance{ | |
44 | HostName: "serveregistrar2.acme.org", | |
45 | Port: 8080, | |
46 | App: appNameTest, | |
47 | IPAddr: "192.168.0.2", | |
48 | VipAddress: "192.168.0.2", | |
49 | SecureVipAddress: "192.168.0.2", | |
50 | HealthCheckUrl: "http://serveregistrar2.acme.org:8080/healthz", | |
51 | StatusPageUrl: "http://serveregistrar2.acme.org:8080/status", | |
52 | HomePageUrl: "http://serveregistrar2.acme.org:8080/", | |
53 | Status: fargo.UP, | |
54 | DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, | |
55 | } | |
56 | ) | |
57 | ||
58 | var _ fargoConnection = (*testConnection)(nil) | |
59 | ||
60 | func (c *testConnection) RegisterInstance(i *fargo.Instance) error { | |
61 | if c.errRegister == nil { | |
62 | for _, instance := range c.instances { | |
63 | if reflect.DeepEqual(*instance, *i) { | |
64 | return errors.New("already registered") | |
65 | } | |
66 | } | |
67 | ||
68 | c.instances = append(c.instances, i) | |
69 | } | |
70 | return c.errRegister | |
71 | } | |
72 | ||
73 | func (c *testConnection) HeartBeatInstance(i *fargo.Instance) error { | |
74 | return c.errHeartbeat | |
75 | } | |
76 | ||
77 | func (c *testConnection) DeregisterInstance(i *fargo.Instance) error { | |
78 | if c.errDeregister == nil { | |
79 | var newInstances []*fargo.Instance | |
80 | for _, instance := range c.instances { | |
81 | if reflect.DeepEqual(*instance, *i) { | |
82 | continue | |
83 | } | |
84 | newInstances = append(newInstances, instance) | |
85 | } | |
86 | if len(newInstances) == len(c.instances) { | |
87 | return errors.New("not registered") | |
88 | } | |
89 | ||
90 | c.instances = newInstances | |
91 | } | |
92 | return c.errDeregister | |
93 | } | |
94 | ||
95 | func (c *testConnection) ReregisterInstance(ins *fargo.Instance) error { | |
96 | return nil | |
97 | } | |
98 | ||
99 | func (c *testConnection) ScheduleAppUpdates(name string, await bool, done <-chan struct{}) <-chan fargo.AppUpdate { | |
100 | updatec := make(chan fargo.AppUpdate, 1) | |
101 | updatec <- fargo.AppUpdate{App: c.application, Err: c.errApplication} | |
102 | return updatec | |
103 | } | |
104 | ||
105 | func (c *testConnection) GetApp(name string) (*fargo.Application, error) { | |
106 | return &fargo.Application{Name: appNameTest, Instances: c.instances}, c.errInstances | |
107 | } |