func deleteTopicHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { log.Printf("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* producers := getProducers(topicName) // remove the topic from all the lookupds for _, addr := range lookupdHTTPAddrs { endpoint := fmt.Sprintf("http://%s/delete_topic?topic=%s", addr, url.QueryEscape(topicName)) log.Printf("LOOKUPD: querying %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) continue } } // now remove the topic from all the producers for _, addr := range producers { endpoint := fmt.Sprintf("http://%s/delete_topic?topic=%s", addr, url.QueryEscape(topicName)) log.Printf("NSQD: querying %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: nsqd %s - %s", endpoint, err.Error()) continue } } NotifyAdminAction("delete_topic", topicName, "", "", req) http.Redirect(w, req, rd, 302) }
func tombstoneTopicProducerHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { log.Printf("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 lookupdHTTPAddrs { endpoint := fmt.Sprintf("http://%s/tombstone_topic_producer?topic=%s&node=%s", addr, url.QueryEscape(topicName), url.QueryEscape(node)) log.Printf("LOOKUPD: querying %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) } } // delete the topic on the producer endpoint := fmt.Sprintf("http://%s/delete_topic?topic=%s", node, url.QueryEscape(topicName)) log.Printf("NSQD: querying %s", endpoint) _, err = util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: nsqd %s - %s", endpoint, err.Error()) } NotifyAdminAction("tombstone_topic_producer", topicName, "", node, req) http.Redirect(w, req, rd, 302) }
// 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.ApiRequest(endpoint) 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.ApiRequest(endpoint) 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 pauseChannelHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { log.Printf("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 } producers := getProducers(topicName) for _, addr := range producers { endpoint := fmt.Sprintf("http://%s%s?topic=%s&channel=%s", addr, req.URL.Path, url.QueryEscape(topicName), url.QueryEscape(channelName)) log.Printf("NSQD: calling %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: nsqd %s - %s", endpoint, err.Error()) continue } } NotifyAdminAction(strings.TrimLeft(req.URL.Path, "/"), topicName, channelName, "", req) http.Redirect(w, req, fmt.Sprintf("/topic/%s/%s", url.QueryEscape(topicName), url.QueryEscape(channelName)), 302) }
func emptyTopicHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { log.Printf("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 } producers := getProducers(topicName) for _, addr := range producers { endpoint := fmt.Sprintf("http://%s/empty_topic?topic=%s", addr, url.QueryEscape(topicName)) log.Printf("NSQD: calling %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: nsqd %s - %s", endpoint, err.Error()) continue } } NotifyAdminAction("empty_topic", topicName, "", "", req) http.Redirect(w, req, fmt.Sprintf("/topic/%s", url.QueryEscape(topicName)), 302) }
func TestTombstonedNodes(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) tcpAddr, httpAddr, nsqlookupd := mustStartLookupd() defer nsqlookupd.Exit() nsqlookupd.inactiveProducerTimeout = 50 * time.Millisecond lookupdHTTPAddrs := []string{fmt.Sprintf("%s", httpAddr)} topicName := "inactive_nodes" 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) 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/tombstone_topic_producer?topic=%s&node=%s", httpAddr, topicName, "ip.address:5555") _, err = util.ApiRequest(endpoint) 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) }
func deleteChannelHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { log.Printf("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 lookupdHTTPAddrs { endpoint := fmt.Sprintf("http://%s/delete_channel?topic=%s&channel=%s", addr, url.QueryEscape(topicName), url.QueryEscape(channelName)) log.Printf("LOOKUPD: querying %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) continue } } producers := getProducers(topicName) for _, addr := range producers { endpoint := fmt.Sprintf("http://%s/delete_channel?topic=%s&channel=%s", addr, url.QueryEscape(topicName), url.QueryEscape(channelName)) log.Printf("NSQD: querying %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: nsqd %s - %s", endpoint, err.Error()) continue } } NotifyAdminAction("delete_channel", topicName, channelName, "", req) http.Redirect(w, req, rd, 302) }
func TestTombstoneRecover(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) tcpAddr, httpAddr, nsqlookupd := mustStartLookupd() defer nsqlookupd.Exit() nsqlookupd.tombstoneLifetime = 50 * time.Millisecond topicName := "tombstone_recover" topicName2 := topicName + "2" 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) nsq.Register(topicName2, "channel2").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 = util.ApiRequest(endpoint) assert.Equal(t, err, nil) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err := util.ApiRequest(endpoint) assert.Equal(t, err, nil) producers, _ := data.Get("producers").Array() assert.Equal(t, len(producers), 0) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName2) data, err = util.ApiRequest(endpoint) assert.Equal(t, err, nil) producers, _ = data.Get("producers").Array() assert.Equal(t, len(producers), 1) time.Sleep(55 * time.Millisecond) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err = util.ApiRequest(endpoint) assert.Equal(t, err, nil) producers, _ = data.Get("producers").Array() assert.Equal(t, len(producers), 1) }
func TestChannelUnregister(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) tcpAddr, httpAddr, nsqlookupd := mustStartLookupd() defer nsqlookupd.Exit() topics := nsqlookupd.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 = nsqlookupd.DB.FindRegistrations("topic", topicName, "") assert.Equal(t, len(topics), 1) channels := nsqlookupd.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 = nsqlookupd.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 = nsqlookupd.DB.FindRegistrations("channel", topicName, "*") assert.Equal(t, len(channels), 1) endpoint := fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err := util.ApiRequest(endpoint) assert.Equal(t, err, nil) returnedProducers, err := data.Get("producers").Array() assert.Equal(t, err, nil) assert.Equal(t, len(returnedProducers), 1) }
// 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.ApiRequest(endpoint) 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) address := producer.Get("address").MustString() //TODO: remove for 1.0 broadcastAddress := producer.Get("broadcast_address").MustString() if broadcastAddress == "" { broadcastAddress = address } httpPort := producer.Get("http_port").MustInt() key := fmt.Sprintf("%s:%d", broadcastAddress, 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 TestNsqdToLookupd(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) topicName := "cluster_test" + strconv.Itoa(int(time.Now().Unix())) hostname, err := os.Hostname() if err != nil { t.Fatalf("ERROR: failed to get hostname - %s", err.Error()) } _, err = util.ApiRequest(fmt.Sprintf("http://127.0.0.1:4151/create_topic?topic=%s", topicName)) if err != nil { t.Fatalf(err.Error()) } _, err = util.ApiRequest(fmt.Sprintf("http://127.0.0.1:4151/create_channel?topic=%s&channel=ch", topicName)) if err != nil { t.Fatalf(err.Error()) } // allow some time for nsqd to push info to nsqlookupd time.Sleep(350 * time.Millisecond) data, err := util.ApiRequest("http://127.0.0.1:4161/debug") if err != nil { t.Fatalf(err.Error()) } topicData := data.Get("topic:" + topicName + ":") producers, _ := topicData.Array() assert.Equal(t, len(producers), 1) producer := topicData.GetIndex(0) assert.Equal(t, producer.Get("address").MustString(), hostname) assert.Equal(t, producer.Get("hostname").MustString(), hostname) assert.Equal(t, producer.Get("broadcast_address").MustString(), hostname) assert.Equal(t, producer.Get("tcp_port").MustInt(), 4150) assert.Equal(t, producer.Get("tombstoned").MustBool(), false) channelData := data.Get("channel:" + topicName + ":ch") producers, _ = channelData.Array() assert.Equal(t, len(producers), 1) producer = topicData.GetIndex(0) assert.Equal(t, producer.Get("address").MustString(), hostname) assert.Equal(t, producer.Get("hostname").MustString(), hostname) assert.Equal(t, producer.Get("broadcast_address").MustString(), hostname) assert.Equal(t, producer.Get("tcp_port").MustInt(), 4150) assert.Equal(t, producer.Get("tombstoned").MustBool(), false) data, err = util.ApiRequest("http://127.0.0.1:4161/lookup?topic=" + topicName) if err != nil { t.Fatalf(err.Error()) } producers, _ = data.Get("producers").Array() assert.Equal(t, len(producers), 1) producer = data.Get("producers").GetIndex(0) assert.Equal(t, producer.Get("address").MustString(), hostname) assert.Equal(t, producer.Get("hostname").MustString(), hostname) assert.Equal(t, producer.Get("broadcast_address").MustString(), hostname) assert.Equal(t, producer.Get("tcp_port").MustInt(), 4150) channels, _ := data.Get("channels").Array() assert.Equal(t, len(channels), 1) channel := channels[0].(string) assert.Equal(t, channel, "ch") data, err = util.ApiRequest("http://127.0.0.1:4151/delete_topic?topic=" + topicName) if err != nil { t.Fatalf(err.Error()) } // allow some time for nsqd to push info to nsqlookupd time.Sleep(350 * time.Millisecond) data, err = util.ApiRequest("http://127.0.0.1:4161/lookup?topic=" + topicName) if err != nil { t.Fatalf(err.Error()) } producers, _ = data.Get("producers").Array() assert.Equal(t, len(producers), 0) data, err = util.ApiRequest("http://127.0.0.1:4161/debug") if err != nil { t.Fatalf(err.Error()) } producers, _ = data.Get("topic:" + topicName + ":").Array() assert.Equal(t, len(producers), 0) producers, _ = data.Get("channel:" + topicName + ":ch").Array() assert.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.ApiRequest(endpoint) 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" } address := producer.Get("address").MustString() //TODO: remove for 1.0 hostname := producer.Get("hostname").MustString() broadcastAddress := producer.Get("broadcast_address").MustString() if broadcastAddress == "" { broadcastAddress = address } 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{ Address: address, //TODO: remove for 1.0 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 }
// 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.ApiRequest(endpoint) 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), 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() clientInfo := &ClientInfo{ HostAddress: addr, ClientVersion: client.Get("version").MustString(), ClientIdentifier: fmt.Sprintf("%s:%s", client.Get("name").MustString(), strings.Split(client.Get("remote_address").MustString(), ":")[1]), 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(), } hostChannelStats.Clients = append(hostChannelStats.Clients, clientInfo) channelStats.Clients = append(channelStats.Clients, clientInfo) } 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 }
func createTopicChannelHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { log.Printf("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 || !nsq.IsValidTopicName(topicName) { http.Error(w, "INVALID_TOPIC", 500) return } channelName, err := reqParams.Get("channel") if err != nil || (len(channelName) > 0 && !nsq.IsValidChannelName(channelName)) { http.Error(w, "INVALID_CHANNEL", 500) return } for _, addr := range lookupdHTTPAddrs { endpoint := fmt.Sprintf("http://%s/create_topic?topic=%s", addr, url.QueryEscape(topicName)) log.Printf("LOOKUPD: querying %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) continue } } NotifyAdminAction("create_topic", topicName, "", "", req) if len(channelName) > 0 { for _, addr := range lookupdHTTPAddrs { endpoint := fmt.Sprintf("http://%s/create_channel?topic=%s&channel=%s", addr, url.QueryEscape(topicName), url.QueryEscape(channelName)) log.Printf("LOOKUPD: querying %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: lookupd %s - %s", endpoint, err.Error()) continue } } // TODO: we can remove this when we push new channel information from nsqlookupd -> nsqd producers, _ := lookupd.GetLookupdTopicProducers(topicName, lookupdHTTPAddrs) for _, addr := range producers { endpoint := fmt.Sprintf("http://%s/create_channel?topic=%s&channel=%s", addr, url.QueryEscape(topicName), url.QueryEscape(channelName)) log.Printf("NSQD: querying %s", endpoint) _, err := util.ApiRequest(endpoint) if err != nil { log.Printf("ERROR: nsqd %s - %s", endpoint, err.Error()) continue } } NotifyAdminAction("create_channel", topicName, channelName, "", req) } http.Redirect(w, req, "/lookup", 302) }
func TestBasicLookupd(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) tcpAddr, httpAddr, nsqlookupd := mustStartLookupd() defer nsqlookupd.Exit() topics := nsqlookupd.DB.FindRegistrations("topic", "*", "*") assert.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").Write(conn) v, err := nsq.ReadResponse(conn) assert.Equal(t, err, nil) assert.Equal(t, v, []byte("OK")) endpoint := fmt.Sprintf("http://%s/nodes", httpAddr) data, err := util.ApiRequest(endpoint) log.Printf("got %v", data) returnedProducers, err := data.Get("producers").Array() assert.Equal(t, err, nil) assert.Equal(t, len(returnedProducers), 1) topics = nsqlookupd.DB.FindRegistrations("topic", topicName, "") assert.Equal(t, len(topics), 1) producers := nsqlookupd.DB.FindProducers("topic", topicName, "") assert.Equal(t, len(producers), 1) producer := producers[0] assert.Equal(t, producer.peerInfo.Address, "ip.address") //TODO: remove for 1.0 assert.Equal(t, producer.peerInfo.BroadcastAddress, "ip.address") assert.Equal(t, producer.peerInfo.Hostname, "ip.address") assert.Equal(t, producer.peerInfo.TcpPort, tcpPort) assert.Equal(t, producer.peerInfo.HttpPort, httpPort) endpoint = fmt.Sprintf("http://%s/topics", httpAddr) data, err = util.ApiRequest(endpoint) assert.Equal(t, err, nil) returnedTopics, err := data.Get("topics").Array() log.Printf("got returnedTopics %v", returnedTopics) assert.Equal(t, err, nil) assert.Equal(t, len(returnedTopics), 1) endpoint = fmt.Sprintf("http://%s/lookup?topic=%s", httpAddr, topicName) data, err = util.ApiRequest(endpoint) assert.Equal(t, err, nil) returnedChannels, err := data.Get("channels").Array() assert.Equal(t, err, nil) assert.Equal(t, len(returnedChannels), 1) returnedProducers, err = data.Get("producers").Array() log.Printf("got returnedProducers %v", returnedProducers) assert.Equal(t, err, nil) assert.Equal(t, len(returnedProducers), 1) for i := range returnedProducers { producer := data.Get("producers").GetIndex(i) log.Printf("producer %v", producer) assert.Equal(t, err, nil) port, err := producer.Get("tcp_port").Int() assert.Equal(t, err, nil) assert.Equal(t, port, tcpPort) port, err = producer.Get("http_port").Int() assert.Equal(t, err, nil) assert.Equal(t, port, httpPort) address, err := producer.Get("address").String() //TODO: remove for 1.0 broadcastaddress, err := producer.Get("broadcast_address").String() assert.Equal(t, err, nil) assert.Equal(t, address, "ip.address") assert.Equal(t, broadcastaddress, "ip.address") ver, err := producer.Get("version").String() assert.Equal(t, err, nil) assert.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.ApiRequest(endpoint) assert.Equal(t, err, nil) returnedChannels, err = data.Get("channels").Array() assert.Equal(t, err, nil) assert.Equal(t, len(returnedChannels), 1) returnedProducers, err = data.Get("producers").Array() assert.Equal(t, err, nil) assert.Equal(t, len(returnedProducers), 0) }