func TestTombstoneRecover(t *testing.T) { opts := NewNSQLookupdOptions() opts.Logger = newTestLogger(t) opts.TombstoneLifetime = 50 * time.Millisecond tcpAddr, httpAddr, nsqlookupd := mustStartLookupd(opts) defer nsqlookupd.Exit() topicName := "tombstone_recover" topicName2 := topicName + "2" conn := mustConnectLookupd(t, tcpAddr) defer conn.Close() identify(t, conn, "ip.address", 5000, 5555, "fake-version") nsq.Register(topicName, "channel1").WriteTo(conn) _, err := nsq.ReadResponse(conn) equal(t, err, nil) nsq.Register(topicName2, "channel2").WriteTo(conn) _, err = nsq.ReadResponse(conn) equal(t, err, nil) endpoint := fmt.Sprintf("http://%s/topic/tombstone?topic=%s&node=%s", httpAddr, topicName, "ip.address:5555") _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) equal(t, err, nil) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) equal(t, err, nil) producers, _ := data.Get("producers").Array() equal(t, len(producers), 0) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName2) data, err = util.APIRequestNegotiateV1("GET", endpoint, nil) equal(t, err, nil) producers, _ = data.Get("producers").Array() equal(t, len(producers), 1) time.Sleep(75 * time.Millisecond) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err = util.APIRequestNegotiateV1("GET", endpoint, nil) equal(t, err, nil) producers, _ = data.Get("producers").Array() equal(t, len(producers), 1) }
// GetLookupdTopicChannels returns a []string containing a union of the channels // from all the given lookupd for the given topic func GetLookupdTopicChannels(topic string, lookupdHTTPAddrs []string) ([]string, error) { success := false allChannels := make([]string, 0) var lock sync.Mutex var wg sync.WaitGroup for _, addr := range lookupdHTTPAddrs { wg.Add(1) endpoint := fmt.Sprintf("http://%s/channels?topic=%s", addr, url.QueryEscape(topic)) log.Printf("LOOKUPD: querying %s", endpoint) go func(endpoint string) { data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) lock.Lock() defer lock.Unlock() defer wg.Done() if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) return } success = true // {"data":{"channels":["test"]}} channels, _ := data.Get("channels").StringArray() allChannels = util.StringUnion(allChannels, channels) }(endpoint) } wg.Wait() sort.Strings(allChannels) if success == false { return nil, errors.New("unable to query any lookupd") } return allChannels, nil }
// GetNSQDTopics returns a []string containing all the topics // produced by the given nsqd func GetNSQDTopics(nsqdHTTPAddrs []string) ([]string, error) { topics := make([]string, 0) var lock sync.Mutex var wg sync.WaitGroup success := false for _, addr := range nsqdHTTPAddrs { wg.Add(1) endpoint := fmt.Sprintf("http://%s/stats?format=json", addr) log.Printf("NSQD: querying %s", endpoint) go func(endpoint string) { data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) lock.Lock() defer lock.Unlock() defer wg.Done() if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) return } success = true topicList, _ := data.Get("topics").Array() for i := range topicList { topicInfo := data.Get("topics").GetIndex(i) topics = util.StringAdd(topics, topicInfo.Get("topic_name").MustString()) } }(endpoint) } wg.Wait() sort.Strings(topics) if success == false { return nil, errors.New("unable to query any nsqd") } return topics, nil }
func (s *httpServer) deleteChannelHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { s.ctx.nsqadmin.logf("ERROR: invalid %s to POST only method", req.Method) http.Error(w, "INVALID_REQUEST", 500) return } reqParams := &util.PostParams{req} topicName, channelName, err := util.GetTopicChannelArgs(reqParams) if err != nil { http.Error(w, err.Error(), 500) return } rd, _ := reqParams.Get("rd") if !strings.HasPrefix(rd, "/") { rd = fmt.Sprintf("/topic/%s", url.QueryEscape(topicName)) } for _, addr := range s.ctx.nsqadmin.opts.NSQLookupdHTTPAddresses { nsqlookupdVersion, err := lookupd.GetVersion(addr) if err != nil { s.ctx.nsqadmin.logf("ERROR: failed to get nsqlookupd %s version - %s", addr, err) } uri := "delete_channel" if !nsqlookupdVersion.Less(v1EndpointVersion) { uri = "channel/delete" } endpoint := fmt.Sprintf("http://%s/%s?topic=%s&channel=%s", addr, uri, url.QueryEscape(topicName), url.QueryEscape(channelName)) s.ctx.nsqadmin.logf("LOOKUPD: querying %s", endpoint) _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) if err != nil { s.ctx.nsqadmin.logf("ERROR: lookupd %s - %s", endpoint, err) continue } } producerAddrs := s.getProducers(topicName) s.performVersionNegotiatedRequestsToNSQD( s.ctx.nsqadmin.opts.NSQLookupdHTTPAddresses, producerAddrs, "delete_channel", "channel/delete", fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))) s.notifyAdminAction("delete_channel", topicName, channelName, "", req) http.Redirect(w, req, rd, 302) }
func (s *httpServer) deleteTopicHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { s.ctx.nsqadmin.logf("ERROR: invalid %s to POST only method", req.Method) http.Error(w, "INVALID_REQUEST", 500) return } reqParams := &util.PostParams{req} topicName, err := reqParams.Get("topic") if err != nil { http.Error(w, "MISSING_ARG_TOPIC", 500) return } rd, _ := reqParams.Get("rd") if !strings.HasPrefix(rd, "/") { rd = "/" } // for topic removal, you need to get all the producers *first* producerAddrs := s.getProducers(topicName) // remove the topic from all the lookupds for _, addr := range s.ctx.nsqadmin.opts.NSQLookupdHTTPAddresses { nsqlookupdVersion, err := lookupd.GetVersion(addr) if err != nil { s.ctx.nsqadmin.logf("ERROR: failed to get nsqlookupd %s version - %s", addr, err) } uri := "delete_topic" if !nsqlookupdVersion.Less(v1EndpointVersion) { uri = "topic/delete" } endpoint := fmt.Sprintf("http://%s/%s?topic=%s", addr, uri, topicName) s.ctx.nsqadmin.logf("LOOKUPD: querying %s", endpoint) _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) if err != nil { s.ctx.nsqadmin.logf("ERROR: lookupd %s - %s", endpoint, err) continue } } s.performVersionNegotiatedRequestsToNSQD( s.ctx.nsqadmin.opts.NSQLookupdHTTPAddresses, producerAddrs, "delete_topic", "topic/delete", fmt.Sprintf("topic=%s", url.QueryEscape(topicName))) s.notifyAdminAction("delete_topic", topicName, "", "", req) http.Redirect(w, req, rd, 302) }
// GetVersion returns a semver.Version object by querying /info func GetVersion(addr string) (*semver.Version, error) { endpoint := fmt.Sprintf("http://%s/info", addr) log.Printf("version negotiation %s", endpoint) info, err := util.APIRequestNegotiateV1("GET", endpoint, nil) if err != nil { log.Printf("ERROR: %s - %s", endpoint, err) return nil, err } version := info.Get("version").MustString("unknown") return semver.Parse(version) }
func TestTombstoneUnregister(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) options := NewNSQLookupdOptions() options.TombstoneLifetime = 50 * time.Millisecond tcpAddr, httpAddr, nsqlookupd := mustStartLookupd(options) defer nsqlookupd.Exit() topicName := "tombstone_unregister" conn := mustConnectLookupd(t, tcpAddr) identify(t, conn, "ip.address", 5000, 5555, "fake-version") nsq.Register(topicName, "channel1").WriteTo(conn) _, err := nsq.ReadResponse(conn) assert.Equal(t, err, nil) endpoint := fmt.Sprintf("http://%s/topic/tombstone?topic=%s&node=%s", httpAddr, topicName, "ip.address:5555") _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) assert.Equal(t, err, nil) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) assert.Equal(t, err, nil) producers, _ := data.Get("producers").Array() assert.Equal(t, len(producers), 0) nsq.UnRegister(topicName, "").WriteTo(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 = util.APIRequestNegotiateV1("GET", endpoint, nil) assert.Equal(t, err, nil) producers, _ = data.Get("producers").Array() assert.Equal(t, len(producers), 0) }
func TestTombstonedNodes(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) options := NewNSQLookupdOptions() tcpAddr, httpAddr, nsqlookupd := mustStartLookupd(options) defer nsqlookupd.Exit() lookupdHTTPAddrs := []string{fmt.Sprintf("%s", httpAddr)} topicName := "inactive_nodes" conn := mustConnectLookupd(t, tcpAddr) identify(t, conn, "ip.address", 5000, 5555, "fake-version") go func() { for { time.Sleep(5 * time.Millisecond) nsq.Ping().WriteTo(conn) _, err := nsq.ReadResponse(conn) if err != nil { return } } }() nsq.Register(topicName, "channel1").WriteTo(conn) _, err := nsq.ReadResponse(conn) assert.Equal(t, err, nil) producers, _ := lookuputil.GetLookupdProducers(lookupdHTTPAddrs) assert.Equal(t, len(producers), 1) assert.Equal(t, len(producers[0].Topics), 1) assert.Equal(t, producers[0].Topics[0].Topic, topicName) assert.Equal(t, producers[0].Topics[0].Tombstoned, false) endpoint := fmt.Sprintf("http://%s/topic/tombstone?topic=%s&node=%s", httpAddr, topicName, "ip.address:5555") _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) assert.Equal(t, err, nil) producers, _ = lookuputil.GetLookupdProducers(lookupdHTTPAddrs) assert.Equal(t, len(producers), 1) assert.Equal(t, len(producers[0].Topics), 1) assert.Equal(t, producers[0].Topics[0].Topic, topicName) assert.Equal(t, producers[0].Topics[0].Tombstoned, true) conn.Close() }
func TestChannelUnregister(t *testing.T) { opts := NewNSQLookupdOptions() opts.Logger = newTestLogger(t) tcpAddr, httpAddr, nsqlookupd := mustStartLookupd(opts) defer nsqlookupd.Exit() topics := nsqlookupd.DB.FindRegistrations("topic", "*", "*") equal(t, len(topics), 0) topicName := "channel_unregister" conn := mustConnectLookupd(t, tcpAddr) defer conn.Close() tcpPort := 5000 httpPort := 5555 identify(t, conn, "ip.address", tcpPort, httpPort, "fake-version") nsq.Register(topicName, "ch1").WriteTo(conn) v, err := nsq.ReadResponse(conn) equal(t, err, nil) equal(t, v, []byte("OK")) topics = nsqlookupd.DB.FindRegistrations("topic", topicName, "") equal(t, len(topics), 1) channels := nsqlookupd.DB.FindRegistrations("channel", topicName, "*") equal(t, len(channels), 1) nsq.UnRegister(topicName, "ch1").WriteTo(conn) v, err = nsq.ReadResponse(conn) equal(t, err, nil) equal(t, v, []byte("OK")) topics = nsqlookupd.DB.FindRegistrations("topic", topicName, "") 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 = nsqlookupd.DB.FindRegistrations("channel", topicName, "*") equal(t, len(channels), 1) endpoint := fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) equal(t, err, nil) returnedProducers, err := data.Get("producers").Array() equal(t, err, nil) equal(t, len(returnedProducers), 1) }
func TestClientAttributes(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) userAgent := "Test User Agent" options := NewNSQDOptions() options.Verbose = true options.SnappyEnabled = true tcpAddr, httpAddr, nsqd := mustStartNSQD(options) defer nsqd.Exit() conn, err := mustConnectNSQD(tcpAddr) assert.Equal(t, err, nil) data := identify(t, conn, map[string]interface{}{ "snappy": true, "user_agent": userAgent, }, frameTypeResponse) resp := struct { Snappy bool `json:"snappy"` UserAgent string `json:"user_agent"` }{} err = json.Unmarshal(data, &resp) assert.Equal(t, err, nil) assert.Equal(t, resp.Snappy, true) r := snappystream.NewReader(conn, snappystream.SkipVerifyChecksum) w := snappystream.NewWriter(conn) topicName := "test_client_attributes" + strconv.Itoa(int(time.Now().Unix())) sub(t, readWriter{r, w}, topicName, "ch") // need to give nsqd a chance to sub the client time.Sleep(50 * time.Millisecond) testUrl := fmt.Sprintf("http://127.0.0.1:%d/stats?format=json", httpAddr.Port) statsData, err := util.APIRequestNegotiateV1("GET", testUrl, nil) if err != nil { t.Fatalf(err.Error()) } client := statsData.Get("topics").GetIndex(0).Get("channels").GetIndex(0).Get("clients").GetIndex(0) assert.Equal(t, client.Get("user_agent").MustString(), userAgent) assert.Equal(t, client.Get("snappy").MustBool(), true) }
func TestClientAttributes(t *testing.T) { userAgent := "Test User Agent" opts := NewNSQDOptions() opts.Logger = newTestLogger(t) opts.Verbose = true opts.SnappyEnabled = true tcpAddr, httpAddr, nsqd := mustStartNSQD(opts) defer nsqd.Exit() conn, err := mustConnectNSQD(tcpAddr) equal(t, err, nil) defer conn.Close() data := identify(t, conn, map[string]interface{}{ "snappy": true, "user_agent": userAgent, }, frameTypeResponse) resp := struct { Snappy bool `json:"snappy"` UserAgent string `json:"user_agent"` }{} err = json.Unmarshal(data, &resp) equal(t, err, nil) equal(t, resp.Snappy, true) r := snappystream.NewReader(conn, snappystream.SkipVerifyChecksum) w := snappystream.NewWriter(conn) readValidate(t, r, frameTypeResponse, "OK") topicName := "test_client_attributes" + strconv.Itoa(int(time.Now().Unix())) sub(t, readWriter{r, w}, topicName, "ch") testUrl := fmt.Sprintf("http://127.0.0.1:%d/stats?format=json", httpAddr.Port) statsData, err := util.APIRequestNegotiateV1("GET", testUrl, nil) equal(t, err, nil) client := statsData.Get("topics").GetIndex(0).Get("channels").GetIndex(0).Get("clients").GetIndex(0) equal(t, client.Get("user_agent").MustString(), userAgent) equal(t, client.Get("snappy").MustBool(), true) }
// GetLookupdTopicProducers returns a []string of the broadcast_address:http_port of all the // producers for a given topic by unioning the results returned from the given lookupd func GetLookupdTopicProducers(topic string, lookupdHTTPAddrs []string) ([]string, error) { success := false allSources := make([]string, 0) var lock sync.Mutex var wg sync.WaitGroup for _, addr := range lookupdHTTPAddrs { wg.Add(1) endpoint := fmt.Sprintf("http://%s/lookup?topic=%s", addr, url.QueryEscape(topic)) log.Printf("LOOKUPD: querying %s", endpoint) go func(endpoint string) { data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) lock.Lock() defer lock.Unlock() defer wg.Done() if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) return } success = true producers := data.Get("producers") producersArray, _ := producers.Array() for i := range producersArray { producer := producers.GetIndex(i) broadcastAddress := producer.Get("broadcast_address").MustString() httpPort := producer.Get("http_port").MustInt() key := net.JoinHostPort(broadcastAddress, strconv.Itoa(httpPort)) allSources = util.StringAdd(allSources, key) } }(endpoint) } wg.Wait() if success == false { return nil, errors.New("unable to query any lookupd") } return allSources, nil }
func (s *httpServer) performVersionNegotiatedRequestsToNSQD( nsqlookupdAddrs []string, nsqdAddrs []string, deprecatedURI string, v1URI string, queryString string) { var err error // get producer structs in one set of up-front requests // so we can negotiate versions // // (this returns an empty list if there are no nsqlookupd configured) producers, _ := lookupd.GetLookupdProducers(nsqlookupdAddrs) for _, addr := range nsqdAddrs { var nodeVer *semver.Version uri := deprecatedURI producer := producerSearch(producers, addr) if producer != nil { nodeVer = producer.VersionObj } else { // we couldn't find the node in our list // so ask it for a version directly nodeVer, err = lookupd.GetVersion(addr) if err != nil { s.ctx.nsqadmin.logf("ERROR: failed to get nsqd %s version - %s", addr, err) } } if nodeVer != nil && !nodeVer.Less(v1EndpointVersion) { uri = v1URI } endpoint := fmt.Sprintf("http://%s/%s?%s", addr, uri, queryString) s.ctx.nsqadmin.logf("NSQD: querying %s", endpoint) _, err := util.APIRequestNegotiateV1("POST", endpoint, nil) if err != nil { s.ctx.nsqadmin.logf("ERROR: nsqd %s - %s", endpoint, err) continue } } }
func TestTombstonedNodes(t *testing.T) { opts := NewNSQLookupdOptions() opts.Logger = newTestLogger(t) tcpAddr, httpAddr, nsqlookupd := mustStartLookupd(opts) defer nsqlookupd.Exit() lookupdHTTPAddrs := []string{fmt.Sprintf("%s", httpAddr)} topicName := "inactive_nodes" conn := mustConnectLookupd(t, tcpAddr) defer conn.Close() identify(t, conn, "ip.address", 5000, 5555, "fake-version") nsq.Register(topicName, "channel1").WriteTo(conn) _, err := nsq.ReadResponse(conn) equal(t, err, nil) producers, _ := lookuputil.GetLookupdProducers(lookupdHTTPAddrs) equal(t, len(producers), 1) equal(t, len(producers[0].Topics), 1) equal(t, producers[0].Topics[0].Topic, topicName) equal(t, producers[0].Topics[0].Tombstoned, false) endpoint := fmt.Sprintf("http://%s/topic/tombstone?topic=%s&node=%s", httpAddr, topicName, "ip.address:5555") _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) equal(t, err, nil) producers, _ = lookuputil.GetLookupdProducers(lookupdHTTPAddrs) equal(t, len(producers), 1) equal(t, len(producers[0].Topics), 1) equal(t, producers[0].Topics[0].Topic, topicName) equal(t, producers[0].Topics[0].Tombstoned, true) }
func TestNsqdToLookupd(t *testing.T) { topicName := "cluster_test" + strconv.Itoa(int(time.Now().Unix())) hostname, err := os.Hostname() equal(t, err, nil) url := fmt.Sprintf("http://127.0.0.1:4151/topic/create?topic=%s", topicName) _, err = util.APIRequestNegotiateV1("POST", url, nil) equal(t, err, nil) url = fmt.Sprintf("http://127.0.0.1:4151/channel/create?topic=%s&channel=ch", topicName) _, err = util.APIRequestNegotiateV1("POST", url, nil) equal(t, err, nil) // allow some time for nsqd to push info to nsqlookupd time.Sleep(350 * time.Millisecond) data, err := util.APIRequestNegotiateV1("GET", "http://127.0.0.1:4161/debug", nil) equal(t, err, nil) topicData := data.Get("topic:" + topicName + ":") producers, _ := topicData.Array() equal(t, len(producers), 1) producer := topicData.GetIndex(0) equal(t, producer.Get("hostname").MustString(), hostname) equal(t, producer.Get("broadcast_address").MustString(), hostname) equal(t, producer.Get("tcp_port").MustInt(), 4150) equal(t, producer.Get("tombstoned").MustBool(), false) channelData := data.Get("channel:" + topicName + ":ch") producers, _ = channelData.Array() equal(t, len(producers), 1) producer = topicData.GetIndex(0) equal(t, producer.Get("hostname").MustString(), hostname) equal(t, producer.Get("broadcast_address").MustString(), hostname) equal(t, producer.Get("tcp_port").MustInt(), 4150) equal(t, producer.Get("tombstoned").MustBool(), false) data, err = util.APIRequestNegotiateV1("GET", "http://127.0.0.1:4161/lookup?topic="+topicName, nil) equal(t, err, nil) producers, _ = data.Get("producers").Array() equal(t, len(producers), 1) producer = data.Get("producers").GetIndex(0) equal(t, producer.Get("hostname").MustString(), hostname) equal(t, producer.Get("broadcast_address").MustString(), hostname) equal(t, producer.Get("tcp_port").MustInt(), 4150) channels, _ := data.Get("channels").Array() equal(t, len(channels), 1) channel := channels[0].(string) equal(t, channel, "ch") data, err = util.APIRequestNegotiateV1("POST", "http://127.0.0.1:4151/topic/delete?topic="+topicName, nil) equal(t, err, nil) // allow some time for nsqd to push info to nsqlookupd time.Sleep(350 * time.Millisecond) data, err = util.APIRequestNegotiateV1("GET", "http://127.0.0.1:4161/lookup?topic="+topicName, nil) equal(t, err, nil) producers, _ = data.Get("producers").Array() equal(t, len(producers), 0) data, err = util.APIRequestNegotiateV1("GET", "http://127.0.0.1:4161/debug", nil) equal(t, err, nil) producers, _ = data.Get("topic:" + topicName + ":").Array() equal(t, len(producers), 0) producers, _ = data.Get("channel:" + topicName + ":ch").Array() equal(t, len(producers), 0) }
// GetLookupdProducers returns a slice of pointers to Producer structs // containing metadata for each node connected to given lookupds func GetLookupdProducers(lookupdHTTPAddrs []string) ([]*Producer, error) { success := false allProducers := make(map[string]*Producer, 0) output := make([]*Producer, 0) maxVersion, _ := semver.Parse("0.0.0") var lock sync.Mutex var wg sync.WaitGroup for _, addr := range lookupdHTTPAddrs { wg.Add(1) endpoint := fmt.Sprintf("http://%s/nodes", addr) log.Printf("LOOKUPD: querying %s", endpoint) go func(addr string, endpoint string) { data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) lock.Lock() defer lock.Unlock() defer wg.Done() if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) return } success = true producers := data.Get("producers") producersArray, _ := producers.Array() for i := range producersArray { producer := producers.GetIndex(i) remoteAddress := producer.Get("remote_address").MustString() if remoteAddress == "" { remoteAddress = "NA" } hostname := producer.Get("hostname").MustString() broadcastAddress := producer.Get("broadcast_address").MustString() httpPort := producer.Get("http_port").MustInt() tcpPort := producer.Get("tcp_port").MustInt() key := fmt.Sprintf("%s:%d:%d", broadcastAddress, httpPort, tcpPort) p, ok := allProducers[key] if !ok { var tombstones []bool var topics ProducerTopics topicList, _ := producer.Get("topics").Array() tombstoneList, err := producer.Get("tombstones").Array() if err != nil { // backwards compatibility with nsqlookupd < v0.2.22 tombstones = make([]bool, len(topicList)) } else { for _, t := range tombstoneList { tombstones = append(tombstones, t.(bool)) } } for i, t := range topicList { topics = append(topics, ProducerTopic{ Topic: t.(string), Tombstoned: tombstones[i], }) } sort.Sort(topics) version := producer.Get("version").MustString("unknown") versionObj, err := semver.Parse(version) if err != nil { versionObj = maxVersion } if maxVersion.Less(versionObj) { maxVersion = versionObj } p = &Producer{ Hostname: hostname, BroadcastAddress: broadcastAddress, TcpPort: tcpPort, HttpPort: httpPort, Version: version, VersionObj: versionObj, Topics: topics, } allProducers[key] = p output = append(output, p) } p.RemoteAddresses = append(p.RemoteAddresses, fmt.Sprintf("%s/%s", addr, remoteAddress)) } }(addr, endpoint) } wg.Wait() for _, producer := range allProducers { if producer.VersionObj.Less(maxVersion) { producer.OutOfDate = true } } sort.Sort(ProducersByHost{output}) if success == false { return nil, errors.New("unable to query any lookupd") } return output, nil }
func (s *httpServer) tombstoneTopicProducerHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { s.ctx.nsqadmin.logf("ERROR: invalid %s to POST only method", req.Method) http.Error(w, "INVALID_REQUEST", 500) return } reqParams := &util.PostParams{req} topicName, err := reqParams.Get("topic") if err != nil { http.Error(w, "MISSING_ARG_TOPIC", 500) return } node, err := reqParams.Get("node") if err != nil { http.Error(w, "MISSING_ARG_NODE", 500) return } rd, _ := reqParams.Get("rd") if !strings.HasPrefix(rd, "/") { rd = "/" } // tombstone the topic on all the lookupds for _, addr := range s.ctx.nsqadmin.opts.NSQLookupdHTTPAddresses { nsqlookupdVersion, err := lookupd.GetVersion(addr) if err != nil { s.ctx.nsqadmin.logf("ERROR: failed to get nsqlookupd %s version - %s", addr, err) } uri := "tombstone_topic_producer" if !nsqlookupdVersion.Less(v1EndpointVersion) { uri = "topic/tombstone" } endpoint := fmt.Sprintf("http://%s/%s?topic=%s&node=%s", addr, uri, url.QueryEscape(topicName), url.QueryEscape(node)) s.ctx.nsqadmin.logf("LOOKUPD: querying %s", endpoint) _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) if err != nil { s.ctx.nsqadmin.logf("ERROR: lookupd %s - %s", endpoint, err) } } nsqdVersion, err := lookupd.GetVersion(node) if err != nil { s.ctx.nsqadmin.logf("ERROR: failed to get nsqd %s version - %s", node, err) } uri := "delete_topic" if !nsqdVersion.Less(v1EndpointVersion) { uri = "topic/delete" } // delete the topic on the producer endpoint := fmt.Sprintf("http://%s/%s?topic=%s", node, uri, url.QueryEscape(topicName)) s.ctx.nsqadmin.logf("NSQD: querying %s", endpoint) _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) if err != nil { s.ctx.nsqadmin.logf("ERROR: nsqd %s - %s", endpoint, err) } s.notifyAdminAction("tombstone_topic_producer", topicName, "", node, req) http.Redirect(w, req, rd, 302) }
func (s *httpServer) createTopicChannelHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { s.ctx.nsqadmin.logf("ERROR: invalid %s to POST only method", req.Method) http.Error(w, "INVALID_REQUEST", 500) return } reqParams := &util.PostParams{req} topicName, err := reqParams.Get("topic") if err != nil || !util.IsValidTopicName(topicName) { http.Error(w, "INVALID_TOPIC", 500) return } channelName, err := reqParams.Get("channel") if err != nil || (len(channelName) > 0 && !util.IsValidChannelName(channelName)) { http.Error(w, "INVALID_CHANNEL", 500) return } for _, addr := range s.ctx.nsqadmin.opts.NSQLookupdHTTPAddresses { nsqlookupdVersion, err := lookupd.GetVersion(addr) if err != nil { s.ctx.nsqadmin.logf("ERROR: failed to get nsqlookupd %s version - %s", addr, err) } uri := "create_topic" if !nsqlookupdVersion.Less(v1EndpointVersion) { uri = "topic/create" } endpoint := fmt.Sprintf("http://%s/%s?topic=%s", addr, uri, url.QueryEscape(topicName)) s.ctx.nsqadmin.logf("LOOKUPD: querying %s", endpoint) _, err = util.APIRequestNegotiateV1("POST", endpoint, nil) if err != nil { s.ctx.nsqadmin.logf("ERROR: lookupd %s - %s", endpoint, err) continue } if len(channelName) > 0 { uri := "create_channel" if !nsqlookupdVersion.Less(v1EndpointVersion) { uri = "channel/create" } endpoint := fmt.Sprintf("http://%s/%s?topic=%s&channel=%s", addr, uri, url.QueryEscape(topicName), url.QueryEscape(channelName)) s.ctx.nsqadmin.logf("LOOKUPD: querying %s", endpoint) _, err := util.APIRequestNegotiateV1("POST", endpoint, nil) if err != nil { s.ctx.nsqadmin.logf("ERROR: lookupd %s - %s", endpoint, err) continue } } } s.notifyAdminAction("create_topic", topicName, "", "", req) if len(channelName) > 0 { // TODO: we can remove this when we push new channel information from nsqlookupd -> nsqd producerAddrs, _ := lookupd.GetLookupdTopicProducers(topicName, s.ctx.nsqadmin.opts.NSQLookupdHTTPAddresses) s.performVersionNegotiatedRequestsToNSQD( s.ctx.nsqadmin.opts.NSQLookupdHTTPAddresses, producerAddrs, "create_channel", "channel/create", fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))) s.notifyAdminAction("create_channel", topicName, channelName, "", req) } http.Redirect(w, req, "/lookup", 302) }
func TestBasicLookupd(t *testing.T) { opts := NewNSQLookupdOptions() opts.Logger = newTestLogger(t) tcpAddr, httpAddr, nsqlookupd := mustStartLookupd(opts) defer nsqlookupd.Exit() topics := nsqlookupd.DB.FindRegistrations("topic", "*", "*") equal(t, len(topics), 0) topicName := "connectmsg" conn := mustConnectLookupd(t, tcpAddr) tcpPort := 5000 httpPort := 5555 identify(t, conn, "ip.address", tcpPort, httpPort, "fake-version") nsq.Register(topicName, "channel1").WriteTo(conn) v, err := nsq.ReadResponse(conn) equal(t, err, nil) equal(t, v, []byte("OK")) endpoint := fmt.Sprintf("http://%s/nodes", httpAddr) data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) t.Logf("got %v", data) returnedProducers, err := data.Get("producers").Array() equal(t, err, nil) equal(t, len(returnedProducers), 1) topics = nsqlookupd.DB.FindRegistrations("topic", topicName, "") equal(t, len(topics), 1) producers := nsqlookupd.DB.FindProducers("topic", topicName, "") equal(t, len(producers), 1) producer := producers[0] equal(t, producer.peerInfo.BroadcastAddress, "ip.address") equal(t, producer.peerInfo.Hostname, "ip.address") equal(t, producer.peerInfo.TcpPort, tcpPort) equal(t, producer.peerInfo.HttpPort, httpPort) endpoint = fmt.Sprintf("http://%s/topics", httpAddr) data, err = util.APIRequestNegotiateV1("GET", endpoint, nil) equal(t, err, nil) returnedTopics, err := data.Get("topics").Array() t.Logf("got returnedTopics %v", returnedTopics) equal(t, err, nil) equal(t, len(returnedTopics), 1) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err = util.APIRequestNegotiateV1("GET", endpoint, nil) equal(t, err, nil) returnedChannels, err := data.Get("channels").Array() equal(t, err, nil) equal(t, len(returnedChannels), 1) returnedProducers, err = data.Get("producers").Array() t.Logf("got returnedProducers %v", returnedProducers) equal(t, err, nil) equal(t, len(returnedProducers), 1) for i := range returnedProducers { producer := data.Get("producers").GetIndex(i) t.Logf("producer %v", producer) port, err := producer.Get("tcp_port").Int() equal(t, err, nil) equal(t, port, tcpPort) port, err = producer.Get("http_port").Int() equal(t, err, nil) equal(t, port, httpPort) broadcastaddress, err := producer.Get("broadcast_address").String() equal(t, err, nil) equal(t, broadcastaddress, "ip.address") ver, err := producer.Get("version").String() equal(t, err, nil) equal(t, ver, "fake-version") } conn.Close() time.Sleep(10 * time.Millisecond) // now there should be no producers, but still topic/channel entries data, err = util.APIRequestNegotiateV1("GET", endpoint, nil) equal(t, err, nil) returnedChannels, err = data.Get("channels").Array() equal(t, err, nil) equal(t, len(returnedChannels), 1) returnedProducers, err = data.Get("producers").Array() equal(t, err, nil) equal(t, len(returnedProducers), 0) }
// GetNSQDStats returns aggregate topic and channel stats from the given NSQD instances // // if selectedTopic is empty, this will return stats for *all* topic/channels // and the ChannelStats dict will be keyed by topic + ':' + channel func GetNSQDStats(nsqdHTTPAddrs []string, selectedTopic string) ([]*TopicStats, map[string]*ChannelStats, error) { var lock sync.Mutex var wg sync.WaitGroup topicStatsList := make(TopicStatsList, 0) channelStatsMap := make(map[string]*ChannelStats) success := false for _, addr := range nsqdHTTPAddrs { wg.Add(1) endpoint := fmt.Sprintf("http://%s/stats?format=json", addr) log.Printf("NSQD: querying %s", endpoint) go func(endpoint string, addr string) { data, err := util.APIRequestNegotiateV1("GET", endpoint, nil) lock.Lock() defer lock.Unlock() defer wg.Done() if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) return } success = true topics, _ := data.Get("topics").Array() for i := range topics { t := data.Get("topics").GetIndex(i) topicName := t.Get("topic_name").MustString() if selectedTopic != "" && topicName != selectedTopic { continue } depth := t.Get("depth").MustInt64() backendDepth := t.Get("backend_depth").MustInt64() channels := t.Get("channels").MustArray() e2eProcessingLatency := util.E2eProcessingLatencyAggregateFromJson(t.Get("e2e_processing_latency"), topicName, "", addr) topicStats := &TopicStats{ HostAddress: addr, TopicName: topicName, Depth: depth, BackendDepth: backendDepth, MemoryDepth: depth - backendDepth, MessageCount: t.Get("message_count").MustInt64(), ChannelCount: len(channels), Paused: t.Get("paused").MustBool(), E2eProcessingLatency: e2eProcessingLatency, } topicStatsList = append(topicStatsList, topicStats) for j := range channels { c := t.Get("channels").GetIndex(j) channelName := c.Get("channel_name").MustString() key := channelName if selectedTopic == "" { key = fmt.Sprintf("%s:%s", topicName, channelName) } channelStats, ok := channelStatsMap[key] if !ok { channelStats = &ChannelStats{ HostAddress: addr, TopicName: topicName, ChannelName: channelName, } channelStatsMap[key] = channelStats } depth := c.Get("depth").MustInt64() backendDepth := c.Get("backend_depth").MustInt64() clients := c.Get("clients").MustArray() e2eProcessingLatency := util.E2eProcessingLatencyAggregateFromJson(c.Get("e2e_processing_latency"), topicName, channelName, addr) hostChannelStats := &ChannelStats{ HostAddress: addr, TopicName: topicName, ChannelName: channelName, Depth: depth, BackendDepth: backendDepth, MemoryDepth: depth - backendDepth, Paused: c.Get("paused").MustBool(), InFlightCount: c.Get("in_flight_count").MustInt64(), DeferredCount: c.Get("deferred_count").MustInt64(), MessageCount: c.Get("message_count").MustInt64(), RequeueCount: c.Get("requeue_count").MustInt64(), TimeoutCount: c.Get("timeout_count").MustInt64(), E2eProcessingLatency: e2eProcessingLatency, // TODO: this is sort of wrong; clients should be de-duped // client A that connects to NSQD-a and NSQD-b should only be counted once. right? ClientCount: len(clients), } channelStats.Add(hostChannelStats) for k := range clients { client := c.Get("clients").GetIndex(k) connected := time.Unix(client.Get("connect_ts").MustInt64(), 0) connectedDuration := time.Now().Sub(connected).Seconds() clientId := client.Get("clientId").MustString() if clientId == "" { // TODO: deprecated, remove in 1.0 name := client.Get("name").MustString() remoteAddressParts := strings.Split(client.Get("remote_address").MustString(), ":") port := remoteAddressParts[len(remoteAddressParts)-1] if len(remoteAddressParts) < 2 { port = "NA" } clientId = fmt.Sprintf("%s:%s", name, port) } clientStats := &ClientStats{ HostAddress: addr, RemoteAddress: client.Get("remote_address").MustString(), Version: client.Get("version").MustString(), ClientID: clientId, Hostname: client.Get("hostname").MustString(), UserAgent: client.Get("user_agent").MustString(), ConnectedDuration: time.Duration(int64(connectedDuration)) * time.Second, // truncate to second InFlightCount: client.Get("in_flight_count").MustInt(), ReadyCount: client.Get("ready_count").MustInt(), FinishCount: client.Get("finish_count").MustInt64(), RequeueCount: client.Get("requeue_count").MustInt64(), MessageCount: client.Get("message_count").MustInt64(), SampleRate: int32(client.Get("sample_rate").MustInt()), TLS: client.Get("tls").MustBool(), Deflate: client.Get("deflate").MustBool(), Snappy: client.Get("snappy").MustBool(), Authed: client.Get("authed").MustBool(), AuthIdentity: client.Get("auth_identity").MustString(), AuthIdentityUrl: client.Get("auth_identity_url").MustString(), } hostChannelStats.Clients = append(hostChannelStats.Clients, clientStats) channelStats.Clients = append(channelStats.Clients, clientStats) } sort.Sort(ClientsByHost{hostChannelStats.Clients}) sort.Sort(ClientsByHost{channelStats.Clients}) topicStats.Channels = append(topicStats.Channels, hostChannelStats) } } sort.Sort(TopicStatsByHost{topicStatsList}) }(endpoint, addr) } wg.Wait() if success == false { return nil, nil, errors.New("unable to query any nsqd") } return topicStatsList, channelStatsMap, nil }