func (s *ClientSuite) TestWatchReconnect(c *C) { port, err := etcdrunner.RandomPort() c.Assert(err, IsNil) // clientA is used to register services and instances, and remains connected clientA, etcdAddr, cleanup := testutil.SetupDiscoverdWithEtcd(c) defer cleanup() // clientB is connected to the server which will be restarted, and is used to // test that the watch generates the correct events after reconnecting clientB, killDiscoverd := testutil.BootDiscoverd(c, port, etcdAddr) defer func() { killDiscoverd() }() // create a service with manual leader and some metadata service := "foo" config := &discoverd.ServiceConfig{LeaderType: discoverd.LeaderTypeManual} c.Assert(clientA.AddService(service, config), IsNil) serviceMeta := &discoverd.ServiceMeta{Data: []byte(`{"foo": "bar"}`)} c.Assert(clientA.Service(service).SetMeta(serviceMeta), IsNil) register := func(client *discoverd.Client, addr string, meta map[string]string) (discoverd.Heartbeater, *discoverd.Instance) { inst := &discoverd.Instance{Addr: addr, Proto: "tcp", Meta: meta} hb, err := client.RegisterInstance(service, inst) c.Assert(err, IsNil) return hb, inst } waitForEvent := func(events chan *discoverd.Event, addr string, kind discoverd.EventKind) { for { select { case e := <-events: if e.Kind == kind && (addr == "" || addr == e.Instance.Addr) { return } case <-time.After(10 * time.Second): c.Fatalf("timed out wating for %s event", kind) } } } waitForWatchState := func(ch chan discoverd.WatchState, state discoverd.WatchState) { for { select { case s := <-ch: if s == state { return } case <-time.After(10 * time.Second): c.Fatalf("timed out waiting for watch %s state", state) } } } // register three services register(clientA, ":1111", nil) hb2, _ := register(clientA, ":2222", map[string]string{"foo": "bar"}) hb3, _ := register(clientA, ":3333", nil) // create watches using both clients so we can synchronize assertions eventsA := make(chan *discoverd.Event) watchA, err := clientA.Service(service).Watch(eventsA) c.Assert(err, IsNil) defer watchA.Close() waitForEvent(eventsA, "", discoverd.EventKindCurrent) eventsB := make(chan *discoverd.Event) watchB, err := clientB.Service(service).Watch(eventsB) c.Assert(err, IsNil) defer watchB.Close() waitForEvent(eventsB, "", discoverd.EventKindCurrent) // kill clientB's server and wait for the watch to disconnect stateCh := make(chan discoverd.WatchState) watchB.(*discoverd.Watch).SetStateChannel(stateCh) killDiscoverd() waitForWatchState(stateCh, discoverd.WatchStateDisconnected) // make some changes using clientA // change some metadata c.Assert(hb2.SetMeta(map[string]string{"foo": "baz"}), IsNil) waitForEvent(eventsA, ":2222", discoverd.EventKindUpdate) // register a new instance _, inst := register(clientA, ":4444", nil) waitForEvent(eventsA, ":4444", discoverd.EventKindUp) // set a new leader clientA.Service(service).SetLeader(inst.ID) waitForEvent(eventsA, ":4444", discoverd.EventKindLeader) // unregister an instance hb3.Close() waitForEvent(eventsA, ":3333", discoverd.EventKindDown) // update the service metadata serviceMeta.Data = []byte(`{"foo": "baz"}`) c.Assert(clientA.Service(service).SetMeta(serviceMeta), IsNil) waitForEvent(eventsA, "", discoverd.EventKindServiceMeta) // restart clientB's server and wait for the watch to reconnect _, killDiscoverd = testutil.RunDiscoverdServer(c, port, etcdAddr) waitForWatchState(stateCh, discoverd.WatchStateConnected) type expectedEvent struct { Addr string Kind discoverd.EventKind ServiceMeta *discoverd.ServiceMeta } assertCurrent := func(events chan *discoverd.Event, expected []*expectedEvent) { count := 0 isExpected := func(event *discoverd.Event) bool { for _, e := range expected { if e.Kind != event.Kind { continue } switch event.Kind { case discoverd.EventKindServiceMeta: if reflect.DeepEqual(event.ServiceMeta, e.ServiceMeta) { return true } default: if event.Instance != nil && event.Instance.Addr == e.Addr { return true } } } return false } for { select { case event := <-events: if event.Kind == discoverd.EventKindCurrent { if count != len(expected) { c.Fatalf("expected %d events, got %d", len(expected), count) } return } if !isExpected(event) { c.Fatalf("unexpected event: %+v", event) } count++ case <-time.After(10 * time.Second): c.Fatal("timed out waiting for events") } } } // check watchB emits missed events assertCurrent(eventsB, []*expectedEvent{ {Addr: ":2222", Kind: discoverd.EventKindUpdate}, {Addr: ":4444", Kind: discoverd.EventKindUp}, {Addr: ":4444", Kind: discoverd.EventKindLeader}, {Kind: discoverd.EventKindServiceMeta, ServiceMeta: serviceMeta}, {Addr: ":3333", Kind: discoverd.EventKindDown}, }) }
func TestReconnect(t *testing.T) { discoverdPort, err := etcdrunner.RandomPort() if err != nil { t.Fatal(err) } clientA, etcdAddr, cleanup := testutil.SetupDiscoverdWithEtcd(t) defer cleanup() clientB, killDiscoverd := testutil.BootDiscoverd(t, discoverdPort, etcdAddr) defer func() { clientB.UnregisterAll() clientB.Close() killDiscoverd() }() service1 := "serviceReconnect-1" service2 := "serviceReconnect-2" assert(clientA.Register(service1, ":1111"), t) assert(clientA.Register(service1, ":2222"), t) assert(clientA.Register(service2, ":1111"), t) assert(clientA.Register(service2, ":2222"), t) set1, err := clientB.NewServiceSet(service1) assert(err, t) waitUpdates(t, set1, true, 2)() set2, err := clientB.NewServiceSet(service2) assert(err, t) waitUpdates(t, set2, true, 2)() updates1 := set1.Watch(false) updates2 := set2.Watch(false) reconnCh := clientB.WatchReconnects() defer clientB.UnwatchReconnects(reconnCh) killDiscoverd() waitForConnStatus(t, reconnCh, discoverd.ConnStatusDisconnected) if err := clientB.Register(service1, ":3333"); err != discoverd.ErrDisconnected { t.Fatal("expected ErrDisconnected from clientB, got:", err) } if _, err := clientB.Services(service2, 1); err != discoverd.ErrDisconnected { t.Fatal("expected ErrDisconnected from clientB, got:", err) } assert(clientA.RegisterWithAttributes(service1, ":1111", map[string]string{"foo": "bar"}), t) assert(clientA.Unregister(service1, ":2222"), t) assert(clientA.Unregister(service2, ":1111"), t) assert(clientA.Register(service2, ":3333"), t) _, killDiscoverd = testutil.RunDiscoverdServer(t, discoverdPort, etcdAddr) waitForConnStatus(t, reconnCh, discoverd.ConnStatusConnected) // use goroutines to check for updates so slow watchers don't block the rpc stream updateErrors := make(chan error) go func() { updateErrors <- checkUpdates(updates1, []*agent.ServiceUpdate{ { Name: service1, Addr: "127.0.0.1:1111", Online: true, Attrs: map[string]string{"foo": "bar"}, }, { Name: service1, Addr: "127.0.0.1:2222", Online: false, }, }) }() go func() { updateErrors <- checkUpdates(updates2, []*agent.ServiceUpdate{ { Name: service2, Addr: "127.0.0.1:3333", Online: true, }, { Name: service2, Addr: "127.0.0.1:2222", Online: true, }, { Name: service2, Addr: "127.0.0.1:1111", Online: false, }, }) }() var updateError error for i := 0; i < 2; i++ { if err := <-updateErrors; err != nil && updateError == nil { updateError = err } } if updateError != nil { t.Fatal(updateError) } assert(clientA.Register(service1, ":3333"), t) if err := checkUpdates(updates1, []*agent.ServiceUpdate{{ Name: service1, Addr: "127.0.0.1:3333", Online: true, }}); err != nil { t.Fatal(err) } // wait for one heartbeat time.Sleep(agent.HeartbeatIntervalSecs*time.Second + time.Second) checkServices(t, set1.Services(), []*discoverd.Service{ {Name: service1, Host: "127.0.0.1", Port: "1111", Addr: "127.0.0.1:1111", Attrs: map[string]string{"foo": "bar"}}, {Name: service1, Host: "127.0.0.1", Port: "3333", Addr: "127.0.0.1:3333"}, }) checkServices(t, set2.Services(), []*discoverd.Service{ {Name: service2, Host: "127.0.0.1", Port: "2222", Addr: "127.0.0.1:2222"}, {Name: service2, Host: "127.0.0.1", Port: "3333", Addr: "127.0.0.1:3333"}, }) }