func TestChannelUnregister(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) tcpAddr, httpAddr := mustStartLookupd() defer lookupd.Exit() topics := lookupd.DB.FindRegistrations("topic", "*", "*") assert.Equal(t, len(topics), 0) topicName := "channel_unregister" conn := mustConnectLookupd(t, tcpAddr) tcpPort := 5000 httpPort := 5555 identify(t, conn, "ip.address", tcpPort, httpPort, "fake-version") nsq.Register(topicName, "ch1").Write(conn) v, err := nsq.ReadResponse(conn) assert.Equal(t, err, nil) assert.Equal(t, v, []byte("OK")) topics = lookupd.DB.FindRegistrations("topic", topicName, "") assert.Equal(t, len(topics), 1) channels := lookupd.DB.FindRegistrations("channel", topicName, "*") assert.Equal(t, len(channels), 1) nsq.UnRegister(topicName, "ch1").Write(conn) v, err = nsq.ReadResponse(conn) assert.Equal(t, err, nil) assert.Equal(t, v, []byte("OK")) topics = lookupd.DB.FindRegistrations("topic", topicName, "") assert.Equal(t, len(topics), 1) // we should still have mention of the topic even though there is no producer // (ie. we haven't *deleted* the channel, just unregistered as a producer) channels = lookupd.DB.FindRegistrations("channel", topicName, "*") assert.Equal(t, len(channels), 1) endpoint := fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err := nsq.ApiRequest(endpoint) assert.Equal(t, err, nil) returnedProducers, err := data.Get("producers").Array() assert.Equal(t, err, nil) assert.Equal(t, len(returnedProducers), 1) }
func TestTombstoneUnregister(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) tcpAddr, httpAddr := mustStartLookupd() defer lookupd.Exit() lookupd.tombstoneLifetime = 50 * time.Millisecond topicName := "tombstone_unregister" conn := mustConnectLookupd(t, tcpAddr) identify(t, conn, "ip.address", 5000, 5555, "fake-version") nsq.Register(topicName, "channel1").Write(conn) _, err := nsq.ReadResponse(conn) assert.Equal(t, err, nil) endpoint := fmt.Sprintf("http://%s/tombstone_topic_producer?topic=%s&node=%s", httpAddr, topicName, "ip.address:5555") _, err = nsq.ApiRequest(endpoint) assert.Equal(t, err, nil) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err := nsq.ApiRequest(endpoint) assert.Equal(t, err, nil) producers, _ := data.Get("producers").Array() assert.Equal(t, len(producers), 0) nsq.UnRegister(topicName, "").Write(conn) _, err = nsq.ReadResponse(conn) assert.Equal(t, err, nil) time.Sleep(55 * time.Millisecond) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err = nsq.ApiRequest(endpoint) assert.Equal(t, err, nil) producers, _ = data.Get("producers").Array() assert.Equal(t, len(producers), 0) }
func (n *NSQd) lookupLoop() { syncTopicChan := make(chan *nsq.LookupPeer) hostname, err := os.Hostname() if err != nil { log.Fatalf("ERROR: failed to get hostname - %s", err.Error()) } for _, host := range n.lookupdTCPAddrs { log.Printf("LOOKUP: adding peer %s", host) lookupPeer := nsq.NewLookupPeer(host, func(lp *nsq.LookupPeer) { ci := make(map[string]interface{}) ci["version"] = util.BINARY_VERSION ci["tcp_port"] = n.tcpAddr.Port ci["http_port"] = n.httpAddr.Port ci["address"] = hostname //TODO: drop for 1.0 ci["hostname"] = hostname ci["broadcast_address"] = n.options.broadcastAddress cmd, err := nsq.Identify(ci) if err != nil { lp.Close() return } resp, err := lp.Command(cmd) if err != nil { log.Printf("LOOKUPD(%s): ERROR %s - %s", lp, cmd, err.Error()) } else if bytes.Equal(resp, []byte("E_INVALID")) { log.Printf("LOOKUPD(%s): lookupd returned %s", lp, resp) } else { err = json.Unmarshal(resp, &lp.Info) if err != nil { log.Printf("LOOKUPD(%s): ERROR parsing response - %v", lp, resp) } else { log.Printf("LOOKUPD(%s): peer info %+v", lp, lp.Info) } } go func() { syncTopicChan <- lp }() }) lookupPeer.Command(nil) // start the connection n.lookupPeers = append(n.lookupPeers, lookupPeer) } // for announcements, lookupd determines the host automatically ticker := time.Tick(15 * time.Second) for { select { case <-ticker: // send a heartbeat and read a response (read detects closed conns) for _, lookupPeer := range n.lookupPeers { log.Printf("LOOKUPD(%s): sending heartbeat", lookupPeer) cmd := nsq.Ping() _, err := lookupPeer.Command(cmd) if err != nil { log.Printf("LOOKUPD(%s): ERROR %s - %s", lookupPeer, cmd, err.Error()) } } case val := <-n.notifyChan: var cmd *nsq.Command var branch string switch val.(type) { case *Channel: // notify all nsqlookupds that a new channel exists, or that it's removed branch = "channel" channel := val.(*Channel) if channel.Exiting() == true { cmd = nsq.UnRegister(channel.topicName, channel.name) } else { cmd = nsq.Register(channel.topicName, channel.name) } case *Topic: // notify all nsqlookupds that a new topic exists, or that it's removed branch = "topic" topic := val.(*Topic) if topic.Exiting() == true { cmd = nsq.UnRegister(topic.name, "") } else { cmd = nsq.Register(topic.name, "") } } for _, lookupPeer := range n.lookupPeers { log.Printf("LOOKUPD(%s): %s %s", lookupPeer, branch, cmd) _, err := lookupPeer.Command(cmd) if err != nil { log.Printf("LOOKUPD(%s): ERROR %s - %s", lookupPeer, cmd, err.Error()) } } case lookupPeer := <-syncTopicChan: commands := make([]*nsq.Command, 0) // build all the commands first so we exit the lock(s) as fast as possible nsqd.RLock() for _, topic := range nsqd.topicMap { topic.RLock() if len(topic.channelMap) == 0 { commands = append(commands, nsq.Register(topic.name, "")) } else { for _, channel := range topic.channelMap { commands = append(commands, nsq.Register(channel.topicName, channel.name)) } } topic.RUnlock() } nsqd.RUnlock() for _, cmd := range commands { log.Printf("LOOKUPD(%s): %s", lookupPeer, cmd) _, err := lookupPeer.Command(cmd) if err != nil { log.Printf("LOOKUPD(%s): ERROR %s - %s", lookupPeer, cmd, err.Error()) break } } case <-n.exitChan: goto exit } } exit: log.Printf("LOOKUP: closing") }