// test channel/topic names func TestChannelTopicNames(t *testing.T) { assert.Equal(t, util.IsValidChannelName("test"), true) assert.Equal(t, util.IsValidChannelName("test-with_period."), true) assert.Equal(t, util.IsValidChannelName("test#ephemeral"), true) assert.Equal(t, util.IsValidTopicName("test"), true) assert.Equal(t, util.IsValidTopicName("test-with_period."), true) assert.Equal(t, util.IsValidTopicName("test#ephemeral"), false) assert.Equal(t, util.IsValidTopicName("test:ephemeral"), false) }
func getTopicChan(command string, params []string) (string, string, error) { if len(params) == 0 { return "", "", util.NewFatalClientErr(nil, "E_INVALID", fmt.Sprintf("%s insufficient number of params", command)) } topicName := params[0] var channelName string if len(params) >= 2 { channelName = params[1] } if !util.IsValidTopicName(topicName) { return "", "", util.NewFatalClientErr(nil, "E_BAD_TOPIC", fmt.Sprintf("%s topic name '%s' is not valid", command, topicName)) } if channelName != "" && !util.IsValidChannelName(channelName) { return "", "", util.NewFatalClientErr(nil, "E_BAD_CHANNEL", fmt.Sprintf("%s channel name '%s' is not valid", command, channelName)) } return topicName, channelName, nil }
func (p *protocolV2) SUB(client *clientV2, params [][]byte) ([]byte, error) { if atomic.LoadInt32(&client.State) != stateInit { return nil, util.NewFatalClientErr(nil, "E_INVALID", "cannot SUB in current state") } if client.HeartbeatInterval <= 0 { return nil, util.NewFatalClientErr(nil, "E_INVALID", "cannot SUB with heartbeats disabled") } if len(params) < 3 { return nil, util.NewFatalClientErr(nil, "E_INVALID", "SUB insufficient number of parameters") } topicName := string(params[1]) if !util.IsValidTopicName(topicName) { return nil, util.NewFatalClientErr(nil, "E_BAD_TOPIC", fmt.Sprintf("SUB topic name %q is not valid", topicName)) } channelName := string(params[2]) if !util.IsValidChannelName(channelName) { return nil, util.NewFatalClientErr(nil, "E_BAD_CHANNEL", fmt.Sprintf("SUB channel name %q is not valid", channelName)) } if err := p.CheckAuth(client, "SUB", topicName, channelName); err != nil { return nil, err } topic := p.ctx.nsqd.GetTopic(topicName) channel := topic.GetChannel(channelName) channel.AddClient(client.ID, client) atomic.StoreInt32(&client.State, stateSubscribed) client.Channel = channel // update message pump client.SubEventChan <- channel return okBytes, nil }
func main() { var selectedMode int flag.Parse() if *showVersion { fmt.Printf("nsq_to_nsq v%s\n", util.BINARY_VERSION) return } if *topic == "" || *channel == "" { log.Fatalf("--topic and --channel are required") } if *destTopic == "" { *destTopic = *topic } if !util.IsValidTopicName(*topic) { log.Fatalf("--topic is invalid") } if !util.IsValidTopicName(*destTopic) { log.Fatalf("--destination-topic is invalid") } if !util.IsValidChannelName(*channel) { log.Fatalf("--channel is invalid") } if len(nsqdTCPAddrs) == 0 && len(lookupdHTTPAddrs) == 0 { log.Fatalf("--nsqd-tcp-address or --lookupd-http-address required") } if len(nsqdTCPAddrs) > 0 && len(lookupdHTTPAddrs) > 0 { log.Fatalf("use --nsqd-tcp-address or --lookupd-http-address not both") } if len(destNsqdTCPAddrs) == 0 { log.Fatalf("--destination-nsqd-tcp-address required") } switch *mode { case "round-robin": selectedMode = ModeRoundRobin case "hostpool": selectedMode = ModeHostPool } termChan := make(chan os.Signal, 1) signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) cfg := nsq.NewConfig() err := util.ParseReaderOpts(cfg, readerOpts) if err != nil { log.Fatalf(err.Error()) } cfg.Set("max_in_flight", *maxInFlight) // TODO: remove, deprecated if hasArg("verbose") { log.Printf("WARNING: --verbose is deprecated in favor of --reader-opt=verbose") cfg.Set("verbose", true) } // TODO: remove, deprecated if hasArg("max-backoff-duration") { log.Printf("WARNING: --max-backoff-duration is deprecated in favor of --reader-opt=max_backoff_duration=X") cfg.Set("max_backoff_duration", *maxBackoffDuration) } r, err := nsq.NewConsumer(*topic, *channel, cfg) if err != nil { log.Fatalf(err.Error()) } r.SetLogger(log.New(os.Stderr, "", log.LstdFlags), nsq.LogLevelInfo) wcfg := nsq.NewConfig() wcfg.Set("heartbeat_interval", nsq.DefaultClientTimeout/2) producers := make(map[string]*nsq.Producer) for _, addr := range destNsqdTCPAddrs { producers[addr] = nsq.NewProducer(addr, wcfg) } handler := &PublishHandler{ addresses: destNsqdTCPAddrs, producers: producers, mode: selectedMode, reqs: make(Durations, 0, *statusEvery), hostPool: hostpool.New(destNsqdTCPAddrs), respChan: make(chan *nsq.ProducerTransaction, len(destNsqdTCPAddrs)), } r.SetConcurrentHandlers(handler, len(destNsqdTCPAddrs)) for i := 0; i < len(destNsqdTCPAddrs); i++ { go handler.responder() } for _, addrString := range nsqdTCPAddrs { err := r.ConnectToNSQD(addrString) if err != nil { log.Fatalf(err.Error()) } } for _, addrString := range lookupdHTTPAddrs { log.Printf("lookupd addr %s", addrString) err := r.ConnectToNSQLookupd(addrString) if err != nil { log.Fatalf(err.Error()) } } for { select { case <-r.StopChan: return case <-termChan: r.Stop() } } }
func (s *httpServer) 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 || !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.context.nsqadmin.options.NSQLookupdHTTPAddresses { 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 } } s.notifyAdminAction("create_topic", topicName, "", "", req) if len(channelName) > 0 { for _, addr := range s.context.nsqadmin.options.NSQLookupdHTTPAddresses { 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, s.context.nsqadmin.options.NSQLookupdHTTPAddresses) 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 } } s.notifyAdminAction("create_channel", topicName, channelName, "", req) } http.Redirect(w, req, "/lookup", 302) }
func (s *httpServer) topicHandler(w http.ResponseWriter, req *http.Request) { var urlRegex = regexp.MustCompile(`^/topic/(.*)$`) matches := urlRegex.FindStringSubmatch(req.URL.Path) if len(matches) == 0 { http.Error(w, "INVALID_TOPIC", 500) return } parts := strings.Split(matches[1], "/") topicName := parts[0] if !util.IsValidTopicName(topicName) { http.Error(w, "INVALID_TOPIC", 500) return } if len(parts) == 2 { channelName := parts[1] if !util.IsValidChannelName(channelName) { http.Error(w, "INVALID_CHANNEL", 500) } else { s.channelHandler(w, req, topicName, channelName) } return } reqParams, err := util.NewReqParams(req) if err != nil { log.Printf("ERROR: failed to parse request params - %s", err.Error()) http.Error(w, "INVALID_REQUEST", 500) return } producers := s.getProducers(topicName) topicStats, channelStats, _ := lookupd.GetNSQDStats(producers, topicName) globalTopicStats := &lookupd.TopicStats{HostAddress: "Total"} for _, t := range topicStats { globalTopicStats.Add(t) } hasE2eLatency := globalTopicStats.E2eProcessingLatency != nil && len(globalTopicStats.E2eProcessingLatency.Percentiles) > 0 var firstTopic *lookupd.TopicStats if len(topicStats) > 0 { firstTopic = topicStats[0] } p := struct { Title string GraphOptions *GraphOptions Version string Topic string TopicProducers []string TopicStats []*lookupd.TopicStats FirstTopic *lookupd.TopicStats GlobalTopicStats *lookupd.TopicStats ChannelStats map[string]*lookupd.ChannelStats HasE2eLatency bool }{ Title: fmt.Sprintf("NSQ %s", topicName), GraphOptions: NewGraphOptions(w, req, reqParams, s.context), Version: util.BINARY_VERSION, Topic: topicName, TopicProducers: producers, TopicStats: topicStats, FirstTopic: firstTopic, GlobalTopicStats: globalTopicStats, ChannelStats: channelStats, HasE2eLatency: hasE2eLatency, } err = templates.T.ExecuteTemplate(w, "topic.html", p) if err != nil { log.Printf("Template Error %s", err.Error()) http.Error(w, "Template Error", 500) } }
func (n *NSQD) LoadMetadata() { fn := fmt.Sprintf(path.Join(n.options.DataPath, "nsqd.%d.dat"), n.options.ID) data, err := ioutil.ReadFile(fn) if err != nil { if !os.IsNotExist(err) { log.Printf("ERROR: failed to read channel metadata from %s - %s", fn, err.Error()) } return } js, err := simplejson.NewJson(data) if err != nil { log.Printf("ERROR: failed to parse metadata - %s", err.Error()) return } topics, err := js.Get("topics").Array() if err != nil { log.Printf("ERROR: failed to parse metadata - %s", err.Error()) return } for ti := range topics { topicJs := js.Get("topics").GetIndex(ti) topicName, err := topicJs.Get("name").String() if err != nil { log.Printf("ERROR: failed to parse metadata - %s", err.Error()) return } if !util.IsValidTopicName(topicName) { log.Printf("WARNING: skipping creation of invalid topic %s", topicName) continue } topic := n.GetTopic(topicName) paused, _ := topicJs.Get("paused").Bool() if paused { topic.Pause() } channels, err := topicJs.Get("channels").Array() if err != nil { log.Printf("ERROR: failed to parse metadata - %s", err.Error()) return } for ci := range channels { channelJs := topicJs.Get("channels").GetIndex(ci) channelName, err := channelJs.Get("name").String() if err != nil { log.Printf("ERROR: failed to parse metadata - %s", err.Error()) return } if !util.IsValidChannelName(channelName) { log.Printf("WARNING: skipping creation of invalid channel %s", channelName) continue } channel := topic.GetChannel(channelName) paused, _ = channelJs.Get("paused").Bool() if paused { channel.Pause() } } } }
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 main() { var selectedMode int flag.Parse() if *showVersion { fmt.Printf("nsq_to_nsq v%s\n", util.BINARY_VERSION) return } if *topic == "" || *channel == "" { log.Fatal("--topic and --channel are required") } if *destTopic == "" { *destTopic = *topic } if !util.IsValidTopicName(*topic) { log.Fatal("--topic is invalid") } if !util.IsValidTopicName(*destTopic) { log.Fatal("--destination-topic is invalid") } if !util.IsValidChannelName(*channel) { log.Fatal("--channel is invalid") } if len(nsqdTCPAddrs) == 0 && len(lookupdHTTPAddrs) == 0 { log.Fatal("--nsqd-tcp-address or --lookupd-http-address required") } if len(nsqdTCPAddrs) > 0 && len(lookupdHTTPAddrs) > 0 { log.Fatal("use --nsqd-tcp-address or --lookupd-http-address not both") } if len(destNsqdTCPAddrs) == 0 { log.Fatal("--destination-nsqd-tcp-address required") } switch *mode { case "round-robin": selectedMode = ModeRoundRobin case "hostpool": selectedMode = ModeHostPool } termChan := make(chan os.Signal, 1) signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM) defaultUA := fmt.Sprintf("nsq_to_nsq/%s go-nsq/%s", util.BINARY_VERSION, nsq.VERSION) cCfg := nsq.NewConfig() cCfg.UserAgent = defaultUA err := util.ParseOpts(cCfg, consumerOpts) if err != nil { log.Fatal(err) } cCfg.MaxInFlight = *maxInFlight // TODO: remove, deprecated if hasArg("max-backoff-duration") { log.Printf("WARNING: --max-backoff-duration is deprecated in favor of --consumer-opt=max_backoff_duration,X") cCfg.MaxBackoffDuration = *maxBackoffDuration } pCfg := nsq.NewConfig() pCfg.UserAgent = defaultUA err = util.ParseOpts(pCfg, producerOpts) if err != nil { log.Fatal(err) } consumer, err := nsq.NewConsumer(*topic, *channel, cCfg) if err != nil { log.Fatal(err) } producers := make(map[string]*nsq.Producer) for _, addr := range destNsqdTCPAddrs { producer, err := nsq.NewProducer(addr, pCfg) if err != nil { log.Fatalf("failed creating producer %s", err) } producers[addr] = producer } perAddressStatus := make(map[string]*timermetrics.TimerMetrics) if len(destNsqdTCPAddrs) == 1 { // disable since there is only one address perAddressStatus[destNsqdTCPAddrs[0]] = timermetrics.NewTimerMetrics(0, "") } else { for _, a := range destNsqdTCPAddrs { perAddressStatus[a] = timermetrics.NewTimerMetrics(*statusEvery, fmt.Sprintf("[%s]:", a)) } } handler := &PublishHandler{ addresses: destNsqdTCPAddrs, producers: producers, mode: selectedMode, hostPool: hostpool.New(destNsqdTCPAddrs), respChan: make(chan *nsq.ProducerTransaction, len(destNsqdTCPAddrs)), perAddressStatus: perAddressStatus, timermetrics: timermetrics.NewTimerMetrics(*statusEvery, "[aggregate]:"), } consumer.AddConcurrentHandlers(handler, len(destNsqdTCPAddrs)) for i := 0; i < len(destNsqdTCPAddrs); i++ { go handler.responder() } err = consumer.ConnectToNSQDs(nsqdTCPAddrs) if err != nil { log.Fatal(err) } err = consumer.ConnectToNSQLookupds(lookupdHTTPAddrs) if err != nil { log.Fatal(err) } for { select { case <-consumer.StopChan: return case <-termChan: consumer.Stop() } } }