github.com/nsqio/nsq@v1.3.0/internal/clusterinfo/data.go (about)

     1  package clusterinfo
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/url"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/blang/semver"
    13  	"github.com/nsqio/nsq/internal/http_api"
    14  	"github.com/nsqio/nsq/internal/lg"
    15  	"github.com/nsqio/nsq/internal/stringy"
    16  )
    17  
    18  type PartialErr interface {
    19  	error
    20  	Errors() []error
    21  }
    22  
    23  type ErrList []error
    24  
    25  func (l ErrList) Error() string {
    26  	var es []string
    27  	for _, e := range l {
    28  		es = append(es, e.Error())
    29  	}
    30  	return strings.Join(es, "\n")
    31  }
    32  
    33  func (l ErrList) Errors() []error {
    34  	return l
    35  }
    36  
    37  type ClusterInfo struct {
    38  	log    lg.AppLogFunc
    39  	client *http_api.Client
    40  }
    41  
    42  func New(log lg.AppLogFunc, client *http_api.Client) *ClusterInfo {
    43  	return &ClusterInfo{
    44  		log:    log,
    45  		client: client,
    46  	}
    47  }
    48  
    49  func (c *ClusterInfo) logf(f string, args ...interface{}) {
    50  	if c.log != nil {
    51  		c.log(lg.INFO, f, args...)
    52  	}
    53  }
    54  
    55  // GetVersion returns a semver.Version object by querying /info
    56  func (c *ClusterInfo) GetVersion(addr string) (semver.Version, error) {
    57  	endpoint := fmt.Sprintf("http://%s/info", addr)
    58  	var resp struct {
    59  		Version string `json:"version"`
    60  	}
    61  	err := c.client.GETV1(endpoint, &resp)
    62  	if err != nil {
    63  		return semver.Version{}, err
    64  	}
    65  	if resp.Version == "" {
    66  		resp.Version = "unknown"
    67  	}
    68  	return semver.Parse(resp.Version)
    69  }
    70  
    71  // GetLookupdTopics returns a []string containing a union of all the topics
    72  // from all the given nsqlookupd
    73  func (c *ClusterInfo) GetLookupdTopics(lookupdHTTPAddrs []string) ([]string, error) {
    74  	var topics []string
    75  	var lock sync.Mutex
    76  	var wg sync.WaitGroup
    77  	var errs []error
    78  
    79  	type respType struct {
    80  		Topics []string `json:"topics"`
    81  	}
    82  
    83  	for _, addr := range lookupdHTTPAddrs {
    84  		wg.Add(1)
    85  		go func(addr string) {
    86  			defer wg.Done()
    87  
    88  			endpoint := fmt.Sprintf("http://%s/topics", addr)
    89  			c.logf("CI: querying nsqlookupd %s", endpoint)
    90  
    91  			var resp respType
    92  			err := c.client.GETV1(endpoint, &resp)
    93  			if err != nil {
    94  				lock.Lock()
    95  				errs = append(errs, err)
    96  				lock.Unlock()
    97  				return
    98  			}
    99  
   100  			lock.Lock()
   101  			defer lock.Unlock()
   102  			topics = append(topics, resp.Topics...)
   103  		}(addr)
   104  	}
   105  	wg.Wait()
   106  
   107  	if len(errs) == len(lookupdHTTPAddrs) {
   108  		return nil, fmt.Errorf("failed to query any nsqlookupd: %s", ErrList(errs))
   109  	}
   110  
   111  	topics = stringy.Uniq(topics)
   112  	sort.Strings(topics)
   113  
   114  	if len(errs) > 0 {
   115  		return topics, ErrList(errs)
   116  	}
   117  	return topics, nil
   118  }
   119  
   120  // GetLookupdTopicChannels returns a []string containing a union of all the channels
   121  // from all the given lookupd for the given topic
   122  func (c *ClusterInfo) GetLookupdTopicChannels(topic string, lookupdHTTPAddrs []string) ([]string, error) {
   123  	var channels []string
   124  	var lock sync.Mutex
   125  	var wg sync.WaitGroup
   126  	var errs []error
   127  
   128  	type respType struct {
   129  		Channels []string `json:"channels"`
   130  	}
   131  
   132  	for _, addr := range lookupdHTTPAddrs {
   133  		wg.Add(1)
   134  		go func(addr string) {
   135  			defer wg.Done()
   136  
   137  			endpoint := fmt.Sprintf("http://%s/channels?topic=%s", addr, url.QueryEscape(topic))
   138  			c.logf("CI: querying nsqlookupd %s", endpoint)
   139  
   140  			var resp respType
   141  			err := c.client.GETV1(endpoint, &resp)
   142  			if err != nil {
   143  				lock.Lock()
   144  				errs = append(errs, err)
   145  				lock.Unlock()
   146  				return
   147  			}
   148  
   149  			lock.Lock()
   150  			defer lock.Unlock()
   151  			channels = append(channels, resp.Channels...)
   152  		}(addr)
   153  	}
   154  	wg.Wait()
   155  
   156  	if len(errs) == len(lookupdHTTPAddrs) {
   157  		return nil, fmt.Errorf("failed to query any nsqlookupd: %s", ErrList(errs))
   158  	}
   159  
   160  	channels = stringy.Uniq(channels)
   161  	sort.Strings(channels)
   162  
   163  	if len(errs) > 0 {
   164  		return channels, ErrList(errs)
   165  	}
   166  	return channels, nil
   167  }
   168  
   169  // GetLookupdProducers returns Producers of all the nsqd connected to the given lookupds
   170  func (c *ClusterInfo) GetLookupdProducers(lookupdHTTPAddrs []string) (Producers, error) {
   171  	var producers []*Producer
   172  	var lock sync.Mutex
   173  	var wg sync.WaitGroup
   174  	var errs []error
   175  
   176  	producersByAddr := make(map[string]*Producer)
   177  	maxVersion, _ := semver.Parse("0.0.0")
   178  
   179  	type respType struct {
   180  		Producers []*Producer `json:"producers"`
   181  	}
   182  
   183  	for _, addr := range lookupdHTTPAddrs {
   184  		wg.Add(1)
   185  		go func(addr string) {
   186  			defer wg.Done()
   187  
   188  			endpoint := fmt.Sprintf("http://%s/nodes", addr)
   189  			c.logf("CI: querying nsqlookupd %s", endpoint)
   190  
   191  			var resp respType
   192  			err := c.client.GETV1(endpoint, &resp)
   193  			if err != nil {
   194  				lock.Lock()
   195  				errs = append(errs, err)
   196  				lock.Unlock()
   197  				return
   198  			}
   199  
   200  			lock.Lock()
   201  			defer lock.Unlock()
   202  			for _, producer := range resp.Producers {
   203  				key := producer.TCPAddress()
   204  				p, ok := producersByAddr[key]
   205  				if !ok {
   206  					producersByAddr[key] = producer
   207  					producers = append(producers, producer)
   208  					if maxVersion.LT(producer.VersionObj) {
   209  						maxVersion = producer.VersionObj
   210  					}
   211  					sort.Sort(producer.Topics)
   212  					p = producer
   213  				}
   214  				p.RemoteAddresses = append(p.RemoteAddresses,
   215  					fmt.Sprintf("%s/%s", addr, producer.Address()))
   216  			}
   217  		}(addr)
   218  	}
   219  	wg.Wait()
   220  
   221  	if len(errs) == len(lookupdHTTPAddrs) {
   222  		return nil, fmt.Errorf("failed to query any nsqlookupd: %s", ErrList(errs))
   223  	}
   224  
   225  	for _, producer := range producersByAddr {
   226  		if producer.VersionObj.LT(maxVersion) {
   227  			producer.OutOfDate = true
   228  		}
   229  	}
   230  	sort.Sort(ProducersByHost{producers})
   231  
   232  	if len(errs) > 0 {
   233  		return producers, ErrList(errs)
   234  	}
   235  	return producers, nil
   236  }
   237  
   238  // GetLookupdTopicProducers returns Producers of all the nsqd for a given topic by
   239  // unioning the nodes returned from the given lookupd
   240  func (c *ClusterInfo) GetLookupdTopicProducers(topic string, lookupdHTTPAddrs []string) (Producers, error) {
   241  	var producers Producers
   242  	var lock sync.Mutex
   243  	var wg sync.WaitGroup
   244  	var errs []error
   245  
   246  	type respType struct {
   247  		Producers Producers `json:"producers"`
   248  	}
   249  
   250  	for _, addr := range lookupdHTTPAddrs {
   251  		wg.Add(1)
   252  		go func(addr string) {
   253  			defer wg.Done()
   254  
   255  			endpoint := fmt.Sprintf("http://%s/lookup?topic=%s", addr, url.QueryEscape(topic))
   256  			c.logf("CI: querying nsqlookupd %s", endpoint)
   257  
   258  			var resp respType
   259  			err := c.client.GETV1(endpoint, &resp)
   260  			if err != nil {
   261  				lock.Lock()
   262  				errs = append(errs, err)
   263  				lock.Unlock()
   264  				return
   265  			}
   266  
   267  			lock.Lock()
   268  			defer lock.Unlock()
   269  			for _, p := range resp.Producers {
   270  				for _, pp := range producers {
   271  					if p.HTTPAddress() == pp.HTTPAddress() {
   272  						goto skip
   273  					}
   274  				}
   275  				producers = append(producers, p)
   276  			skip:
   277  			}
   278  		}(addr)
   279  	}
   280  	wg.Wait()
   281  
   282  	if len(errs) == len(lookupdHTTPAddrs) {
   283  		return nil, fmt.Errorf("failed to query any nsqlookupd: %s", ErrList(errs))
   284  	}
   285  	if len(errs) > 0 {
   286  		return producers, ErrList(errs)
   287  	}
   288  	return producers, nil
   289  }
   290  
   291  // GetNSQDTopics returns a []string containing all the topics produced by the given nsqd
   292  func (c *ClusterInfo) GetNSQDTopics(nsqdHTTPAddrs []string) ([]string, error) {
   293  	var topics []string
   294  	var lock sync.Mutex
   295  	var wg sync.WaitGroup
   296  	var errs []error
   297  
   298  	type respType struct {
   299  		Topics []struct {
   300  			Name string `json:"topic_name"`
   301  		} `json:"topics"`
   302  	}
   303  
   304  	for _, addr := range nsqdHTTPAddrs {
   305  		wg.Add(1)
   306  		go func(addr string) {
   307  			defer wg.Done()
   308  
   309  			endpoint := fmt.Sprintf("http://%s/stats?format=json", addr)
   310  			c.logf("CI: querying nsqd %s", endpoint)
   311  
   312  			var resp respType
   313  			err := c.client.GETV1(endpoint, &resp)
   314  			if err != nil {
   315  				lock.Lock()
   316  				errs = append(errs, err)
   317  				lock.Unlock()
   318  				return
   319  			}
   320  
   321  			lock.Lock()
   322  			defer lock.Unlock()
   323  			for _, topic := range resp.Topics {
   324  				topics = stringy.Add(topics, topic.Name)
   325  			}
   326  		}(addr)
   327  	}
   328  	wg.Wait()
   329  
   330  	if len(errs) == len(nsqdHTTPAddrs) {
   331  		return nil, fmt.Errorf("failed to query any nsqd: %s", ErrList(errs))
   332  	}
   333  
   334  	sort.Strings(topics)
   335  
   336  	if len(errs) > 0 {
   337  		return topics, ErrList(errs)
   338  	}
   339  	return topics, nil
   340  }
   341  
   342  // GetNSQDProducers returns Producers of all the given nsqd
   343  func (c *ClusterInfo) GetNSQDProducers(nsqdHTTPAddrs []string) (Producers, error) {
   344  	var producers Producers
   345  	var lock sync.Mutex
   346  	var wg sync.WaitGroup
   347  	var errs []error
   348  
   349  	type infoRespType struct {
   350  		Version          string `json:"version"`
   351  		BroadcastAddress string `json:"broadcast_address"`
   352  		Hostname         string `json:"hostname"`
   353  		HTTPPort         int    `json:"http_port"`
   354  		TCPPort          int    `json:"tcp_port"`
   355  	}
   356  
   357  	type statsRespType struct {
   358  		Topics []struct {
   359  			Name string `json:"topic_name"`
   360  		} `json:"topics"`
   361  	}
   362  
   363  	for _, addr := range nsqdHTTPAddrs {
   364  		wg.Add(1)
   365  		go func(addr string) {
   366  			defer wg.Done()
   367  
   368  			endpoint := fmt.Sprintf("http://%s/info", addr)
   369  			c.logf("CI: querying nsqd %s", endpoint)
   370  
   371  			var infoResp infoRespType
   372  			err := c.client.GETV1(endpoint, &infoResp)
   373  			if err != nil {
   374  				lock.Lock()
   375  				errs = append(errs, err)
   376  				lock.Unlock()
   377  				return
   378  			}
   379  
   380  			endpoint = fmt.Sprintf("http://%s/stats?format=json&include_clients=false", addr)
   381  			c.logf("CI: querying nsqd %s", endpoint)
   382  
   383  			var statsResp statsRespType
   384  			err = c.client.GETV1(endpoint, &statsResp)
   385  			if err != nil {
   386  				lock.Lock()
   387  				errs = append(errs, err)
   388  				lock.Unlock()
   389  				return
   390  			}
   391  
   392  			var producerTopics ProducerTopics
   393  			for _, t := range statsResp.Topics {
   394  				producerTopics = append(producerTopics, ProducerTopic{Topic: t.Name})
   395  			}
   396  
   397  			version, err := semver.Parse(infoResp.Version)
   398  			if err != nil {
   399  				version, _ = semver.Parse("0.0.0")
   400  			}
   401  
   402  			lock.Lock()
   403  			defer lock.Unlock()
   404  			producers = append(producers, &Producer{
   405  				Version:          infoResp.Version,
   406  				VersionObj:       version,
   407  				BroadcastAddress: infoResp.BroadcastAddress,
   408  				Hostname:         infoResp.Hostname,
   409  				HTTPPort:         infoResp.HTTPPort,
   410  				TCPPort:          infoResp.TCPPort,
   411  				Topics:           producerTopics,
   412  			})
   413  		}(addr)
   414  	}
   415  	wg.Wait()
   416  
   417  	if len(errs) == len(nsqdHTTPAddrs) {
   418  		return nil, fmt.Errorf("failed to query any nsqd: %s", ErrList(errs))
   419  	}
   420  	if len(errs) > 0 {
   421  		return producers, ErrList(errs)
   422  	}
   423  	return producers, nil
   424  }
   425  
   426  // GetNSQDTopicProducers returns Producers containing the addresses of all the nsqd
   427  // that produce the given topic
   428  func (c *ClusterInfo) GetNSQDTopicProducers(topic string, nsqdHTTPAddrs []string) (Producers, error) {
   429  	var producers Producers
   430  	var lock sync.Mutex
   431  	var wg sync.WaitGroup
   432  	var errs []error
   433  
   434  	type infoRespType struct {
   435  		Version          string `json:"version"`
   436  		BroadcastAddress string `json:"broadcast_address"`
   437  		Hostname         string `json:"hostname"`
   438  		HTTPPort         int    `json:"http_port"`
   439  		TCPPort          int    `json:"tcp_port"`
   440  	}
   441  
   442  	type statsRespType struct {
   443  		Topics []struct {
   444  			Name string `json:"topic_name"`
   445  		} `json:"topics"`
   446  	}
   447  
   448  	for _, addr := range nsqdHTTPAddrs {
   449  		wg.Add(1)
   450  		go func(addr string) {
   451  			defer wg.Done()
   452  
   453  			endpoint := fmt.Sprintf("http://%s/stats?format=json&topic=%s&include_clients=false",
   454  				addr, url.QueryEscape(topic))
   455  			c.logf("CI: querying nsqd %s", endpoint)
   456  
   457  			var statsResp statsRespType
   458  			err := c.client.GETV1(endpoint, &statsResp)
   459  			if err != nil {
   460  				lock.Lock()
   461  				errs = append(errs, err)
   462  				lock.Unlock()
   463  				return
   464  			}
   465  
   466  			var producerTopics ProducerTopics
   467  			for _, t := range statsResp.Topics {
   468  				producerTopics = append(producerTopics, ProducerTopic{Topic: t.Name})
   469  			}
   470  
   471  			for _, t := range statsResp.Topics {
   472  				if t.Name == topic {
   473  					endpoint := fmt.Sprintf("http://%s/info", addr)
   474  					c.logf("CI: querying nsqd %s", endpoint)
   475  
   476  					var infoResp infoRespType
   477  					err := c.client.GETV1(endpoint, &infoResp)
   478  					if err != nil {
   479  						lock.Lock()
   480  						errs = append(errs, err)
   481  						lock.Unlock()
   482  						return
   483  					}
   484  
   485  					version, err := semver.Parse(infoResp.Version)
   486  					if err != nil {
   487  						version, _ = semver.Parse("0.0.0")
   488  					}
   489  
   490  					// if BroadcastAddress/HTTPPort are missing, use the values from `addr` for
   491  					// backwards compatibility
   492  
   493  					if infoResp.BroadcastAddress == "" {
   494  						var p string
   495  						infoResp.BroadcastAddress, p, _ = net.SplitHostPort(addr)
   496  						infoResp.HTTPPort, _ = strconv.Atoi(p)
   497  					}
   498  					if infoResp.Hostname == "" {
   499  						infoResp.Hostname, _, _ = net.SplitHostPort(addr)
   500  					}
   501  
   502  					lock.Lock()
   503  					producers = append(producers, &Producer{
   504  						Version:          infoResp.Version,
   505  						VersionObj:       version,
   506  						BroadcastAddress: infoResp.BroadcastAddress,
   507  						Hostname:         infoResp.Hostname,
   508  						HTTPPort:         infoResp.HTTPPort,
   509  						TCPPort:          infoResp.TCPPort,
   510  						Topics:           producerTopics,
   511  					})
   512  					lock.Unlock()
   513  
   514  					return
   515  				}
   516  			}
   517  		}(addr)
   518  	}
   519  	wg.Wait()
   520  
   521  	if len(errs) == len(nsqdHTTPAddrs) {
   522  		return nil, fmt.Errorf("failed to query any nsqd: %s", ErrList(errs))
   523  	}
   524  	if len(errs) > 0 {
   525  		return producers, ErrList(errs)
   526  	}
   527  	return producers, nil
   528  }
   529  
   530  // GetNSQDStats returns aggregate topic and channel stats from the given Producers
   531  //
   532  // if selectedChannel is empty, this will return stats for topic/channel
   533  // if selectedTopic is empty, this will return stats for *all* topic/channels
   534  // if includeClients is false, this will *not* return client stats for channels
   535  // and the ChannelStats dict will be keyed by topic + ':' + channel
   536  func (c *ClusterInfo) GetNSQDStats(producers Producers,
   537  	selectedTopic string, selectedChannel string,
   538  	includeClients bool) ([]*TopicStats, map[string]*ChannelStats, error) {
   539  	var lock sync.Mutex
   540  	var wg sync.WaitGroup
   541  	var topicStatsList TopicStatsList
   542  	var errs []error
   543  
   544  	channelStatsMap := make(map[string]*ChannelStats)
   545  
   546  	type respType struct {
   547  		Topics []*TopicStats `json:"topics"`
   548  	}
   549  
   550  	for _, p := range producers {
   551  		wg.Add(1)
   552  		go func(p *Producer) {
   553  			defer wg.Done()
   554  
   555  			addr := p.HTTPAddress()
   556  
   557  			endpoint := fmt.Sprintf("http://%s/stats?format=json", addr)
   558  			if selectedTopic != "" {
   559  				endpoint += "&topic=" + url.QueryEscape(selectedTopic)
   560  				if selectedChannel != "" {
   561  					endpoint += "&channel=" + url.QueryEscape(selectedChannel)
   562  				}
   563  			}
   564  			if !includeClients {
   565  				endpoint += "&include_clients=false"
   566  			}
   567  
   568  			c.logf("CI: querying nsqd %s", endpoint)
   569  
   570  			var resp respType
   571  			err := c.client.GETV1(endpoint, &resp)
   572  			if err != nil {
   573  				lock.Lock()
   574  				errs = append(errs, err)
   575  				lock.Unlock()
   576  				return
   577  			}
   578  
   579  			lock.Lock()
   580  			defer lock.Unlock()
   581  			for _, topic := range resp.Topics {
   582  				topic.Node = addr
   583  				topic.Hostname = p.Hostname
   584  				topic.MemoryDepth = topic.Depth - topic.BackendDepth
   585  				if selectedTopic != "" && topic.TopicName != selectedTopic {
   586  					continue
   587  				}
   588  				topicStatsList = append(topicStatsList, topic)
   589  
   590  				for _, channel := range topic.Channels {
   591  					channel.Node = addr
   592  					channel.Hostname = p.Hostname
   593  					channel.TopicName = topic.TopicName
   594  					channel.MemoryDepth = channel.Depth - channel.BackendDepth
   595  					key := channel.ChannelName
   596  					if selectedTopic == "" {
   597  						key = fmt.Sprintf("%s:%s", topic.TopicName, channel.ChannelName)
   598  					}
   599  					channelStats, ok := channelStatsMap[key]
   600  					if !ok {
   601  						channelStats = &ChannelStats{
   602  							Node:        addr,
   603  							TopicName:   topic.TopicName,
   604  							ChannelName: channel.ChannelName,
   605  						}
   606  						channelStatsMap[key] = channelStats
   607  					}
   608  					for _, c := range channel.Clients {
   609  						c.Node = addr
   610  					}
   611  					channelStats.Add(channel)
   612  				}
   613  			}
   614  		}(p)
   615  	}
   616  	wg.Wait()
   617  
   618  	if len(errs) == len(producers) {
   619  		return nil, nil, fmt.Errorf("failed to query any nsqd: %s", ErrList(errs))
   620  	}
   621  
   622  	sort.Sort(TopicStatsByHost{topicStatsList})
   623  
   624  	if len(errs) > 0 {
   625  		return topicStatsList, channelStatsMap, ErrList(errs)
   626  	}
   627  	return topicStatsList, channelStatsMap, nil
   628  }
   629  
   630  // TombstoneNodeForTopic tombstones the given node for the given topic on all the given nsqlookupd
   631  // and deletes the topic from the node
   632  func (c *ClusterInfo) TombstoneNodeForTopic(topic string, node string, lookupdHTTPAddrs []string) error {
   633  	var errs []error
   634  
   635  	// tombstone the topic on all the lookupds
   636  	qs := fmt.Sprintf("topic=%s&node=%s", url.QueryEscape(topic), url.QueryEscape(node))
   637  	err := c.nsqlookupdPOST(lookupdHTTPAddrs, "topic/tombstone", qs)
   638  	if err != nil {
   639  		pe, ok := err.(PartialErr)
   640  		if !ok {
   641  			return err
   642  		}
   643  		errs = append(errs, pe.Errors()...)
   644  	}
   645  
   646  	producers, err := c.GetNSQDProducers([]string{node})
   647  	if err != nil {
   648  		pe, ok := err.(PartialErr)
   649  		if !ok {
   650  			return err
   651  		}
   652  		errs = append(errs, pe.Errors()...)
   653  	}
   654  
   655  	// delete the topic on the producer
   656  	qs = fmt.Sprintf("topic=%s", url.QueryEscape(topic))
   657  	err = c.producersPOST(producers, "topic/delete", qs)
   658  	if err != nil {
   659  		pe, ok := err.(PartialErr)
   660  		if !ok {
   661  			return err
   662  		}
   663  		errs = append(errs, pe.Errors()...)
   664  	}
   665  
   666  	if len(errs) > 0 {
   667  		return ErrList(errs)
   668  	}
   669  	return nil
   670  }
   671  
   672  func (c *ClusterInfo) CreateTopicChannel(topicName string, channelName string, lookupdHTTPAddrs []string) error {
   673  	var errs []error
   674  
   675  	// create the topic on all the nsqlookupd
   676  	qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
   677  	err := c.nsqlookupdPOST(lookupdHTTPAddrs, "topic/create", qs)
   678  	if err != nil {
   679  		pe, ok := err.(PartialErr)
   680  		if !ok {
   681  			return err
   682  		}
   683  		errs = append(errs, pe.Errors()...)
   684  	}
   685  
   686  	if len(channelName) > 0 {
   687  		qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))
   688  
   689  		// create the channel on all the nsqlookupd
   690  		err := c.nsqlookupdPOST(lookupdHTTPAddrs, "channel/create", qs)
   691  		if err != nil {
   692  			pe, ok := err.(PartialErr)
   693  			if !ok {
   694  				return err
   695  			}
   696  			errs = append(errs, pe.Errors()...)
   697  		}
   698  
   699  		// create the channel on all the nsqd that produce the topic
   700  		producers, err := c.GetLookupdTopicProducers(topicName, lookupdHTTPAddrs)
   701  		if err != nil {
   702  			pe, ok := err.(PartialErr)
   703  			if !ok {
   704  				return err
   705  			}
   706  			errs = append(errs, pe.Errors()...)
   707  		}
   708  		err = c.producersPOST(producers, "channel/create", qs)
   709  		if err != nil {
   710  			pe, ok := err.(PartialErr)
   711  			if !ok {
   712  				return err
   713  			}
   714  			errs = append(errs, pe.Errors()...)
   715  		}
   716  	}
   717  
   718  	if len(errs) > 0 {
   719  		return ErrList(errs)
   720  	}
   721  	return nil
   722  }
   723  
   724  func (c *ClusterInfo) DeleteTopic(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
   725  	var errs []error
   726  
   727  	// for topic removal, you need to get all the producers _first_
   728  	producers, err := c.GetTopicProducers(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs)
   729  	if err != nil {
   730  		pe, ok := err.(PartialErr)
   731  		if !ok {
   732  			return err
   733  		}
   734  		errs = append(errs, pe.Errors()...)
   735  	}
   736  
   737  	qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
   738  
   739  	// remove the topic from all the nsqlookupd
   740  	err = c.nsqlookupdPOST(lookupdHTTPAddrs, "topic/delete", qs)
   741  	if err != nil {
   742  		pe, ok := err.(PartialErr)
   743  		if !ok {
   744  			return err
   745  		}
   746  		errs = append(errs, pe.Errors()...)
   747  	}
   748  
   749  	// remove the topic from all the nsqd that produce this topic
   750  	err = c.producersPOST(producers, "topic/delete", qs)
   751  	if err != nil {
   752  		pe, ok := err.(PartialErr)
   753  		if !ok {
   754  			return err
   755  		}
   756  		errs = append(errs, pe.Errors()...)
   757  	}
   758  
   759  	if len(errs) > 0 {
   760  		return ErrList(errs)
   761  	}
   762  	return nil
   763  }
   764  
   765  func (c *ClusterInfo) DeleteChannel(topicName string, channelName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
   766  	var errs []error
   767  
   768  	producers, err := c.GetTopicProducers(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs)
   769  	if err != nil {
   770  		pe, ok := err.(PartialErr)
   771  		if !ok {
   772  			return err
   773  		}
   774  		errs = append(errs, pe.Errors()...)
   775  	}
   776  
   777  	qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))
   778  
   779  	// remove the channel from all the nsqlookupd
   780  	err = c.nsqlookupdPOST(lookupdHTTPAddrs, "channel/delete", qs)
   781  	if err != nil {
   782  		pe, ok := err.(PartialErr)
   783  		if !ok {
   784  			return err
   785  		}
   786  		errs = append(errs, pe.Errors()...)
   787  	}
   788  
   789  	// remove the channel from all the nsqd that produce this topic
   790  	err = c.producersPOST(producers, "channel/delete", qs)
   791  	if err != nil {
   792  		pe, ok := err.(PartialErr)
   793  		if !ok {
   794  			return err
   795  		}
   796  		errs = append(errs, pe.Errors()...)
   797  	}
   798  
   799  	if len(errs) > 0 {
   800  		return ErrList(errs)
   801  	}
   802  	return nil
   803  }
   804  
   805  func (c *ClusterInfo) PauseTopic(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
   806  	qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
   807  	return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "topic/pause", qs)
   808  }
   809  
   810  func (c *ClusterInfo) UnPauseTopic(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
   811  	qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
   812  	return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "topic/unpause", qs)
   813  }
   814  
   815  func (c *ClusterInfo) PauseChannel(topicName string, channelName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
   816  	qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))
   817  	return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "channel/pause", qs)
   818  }
   819  
   820  func (c *ClusterInfo) UnPauseChannel(topicName string, channelName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
   821  	qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))
   822  	return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "channel/unpause", qs)
   823  }
   824  
   825  func (c *ClusterInfo) EmptyTopic(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
   826  	qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
   827  	return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "topic/empty", qs)
   828  }
   829  
   830  func (c *ClusterInfo) EmptyChannel(topicName string, channelName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
   831  	qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))
   832  	return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "channel/empty", qs)
   833  }
   834  
   835  func (c *ClusterInfo) actionHelper(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string, uri string, qs string) error {
   836  	var errs []error
   837  
   838  	producers, err := c.GetTopicProducers(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs)
   839  	if err != nil {
   840  		pe, ok := err.(PartialErr)
   841  		if !ok {
   842  			return err
   843  		}
   844  		errs = append(errs, pe.Errors()...)
   845  	}
   846  
   847  	err = c.producersPOST(producers, uri, qs)
   848  	if err != nil {
   849  		pe, ok := err.(PartialErr)
   850  		if !ok {
   851  			return err
   852  		}
   853  		errs = append(errs, pe.Errors()...)
   854  	}
   855  
   856  	if len(errs) > 0 {
   857  		return ErrList(errs)
   858  	}
   859  	return nil
   860  }
   861  
   862  func (c *ClusterInfo) GetProducers(lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) (Producers, error) {
   863  	if len(lookupdHTTPAddrs) != 0 {
   864  		return c.GetLookupdProducers(lookupdHTTPAddrs)
   865  	}
   866  	return c.GetNSQDProducers(nsqdHTTPAddrs)
   867  }
   868  
   869  func (c *ClusterInfo) GetTopicProducers(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) (Producers, error) {
   870  	if len(lookupdHTTPAddrs) != 0 {
   871  		return c.GetLookupdTopicProducers(topicName, lookupdHTTPAddrs)
   872  	}
   873  	return c.GetNSQDTopicProducers(topicName, nsqdHTTPAddrs)
   874  }
   875  
   876  func (c *ClusterInfo) nsqlookupdPOST(addrs []string, uri string, qs string) error {
   877  	var errs []error
   878  	for _, addr := range addrs {
   879  		endpoint := fmt.Sprintf("http://%s/%s?%s", addr, uri, qs)
   880  		c.logf("CI: querying nsqlookupd %s", endpoint)
   881  		err := c.client.POSTV1(endpoint)
   882  		if err != nil {
   883  			errs = append(errs, err)
   884  		}
   885  	}
   886  	if len(errs) > 0 {
   887  		return ErrList(errs)
   888  	}
   889  	return nil
   890  }
   891  
   892  func (c *ClusterInfo) producersPOST(pl Producers, uri string, qs string) error {
   893  	var errs []error
   894  	for _, p := range pl {
   895  		endpoint := fmt.Sprintf("http://%s/%s?%s", p.HTTPAddress(), uri, qs)
   896  		c.logf("CI: querying nsqd %s", endpoint)
   897  		err := c.client.POSTV1(endpoint)
   898  		if err != nil {
   899  			errs = append(errs, err)
   900  		}
   901  	}
   902  	if len(errs) > 0 {
   903  		return ErrList(errs)
   904  	}
   905  	return nil
   906  }