package clusterinfo

import (
    "fmt"
    "net"
    "net/url"
    "sort"
    "strconv"
    "strings"
    "sync"

    "github.com/blang/semver"
    "github.com/nsqio/nsq/internal/http_api"
    "github.com/nsqio/nsq/internal/stringy"
)

var v1EndpointVersion semver.Version

func init() {
    v1EndpointVersion, _ = semver.Parse("0.2.29-alpha")
}

type PartialErr interface {
    error
    Errors() []error
}

type ErrList []error

func (l ErrList) Error() string {
    var es []string
    for _, e := range l {
        es = append(es, e.Error())
    }
    return strings.Join(es, "\n")
}

func (l ErrList) Errors() []error {
    return l
}

type logger interface {
    Output(maxdepth int, s string) error
}

type ClusterInfo struct {
    log    logger
    client *http_api.Client
}

func New(log logger, client *http_api.Client) *ClusterInfo {
    return &ClusterInfo{
        log:    log,
        client: client,
    }
}

func (c *ClusterInfo) logf(f string, args ...interface{}) {
    if c.log == nil {
        return
    }
    c.log.Output(2, fmt.Sprintf(f, args...))
}

// GetVersion returns a semver.Version object by querying /info
func (c *ClusterInfo) GetVersion(addr string) (semver.Version, error) {
    endpoint := fmt.Sprintf("http://%s/info", addr)
    var resp struct {
        Version string `json:'version'`
    }
    err := c.client.NegotiateV1(endpoint, &resp)
    if err != nil {
        return semver.Version{}, err
    }
    if resp.Version == "" {
        resp.Version = "unknown"
    }
    return semver.Parse(resp.Version)
}

// GetLookupdTopics returns a []string containing a union of all the topics
// from all the given nsqlookupd
func (c *ClusterInfo) GetLookupdTopics(lookupdHTTPAddrs []string) ([]string, error) {
    var topics []string
    var lock sync.Mutex
    var wg sync.WaitGroup
    var errs []error

    type respType struct {
        Topics []string `json:"topics"`
    }

    for _, addr := range lookupdHTTPAddrs {
        wg.Add(1)
        go func(addr string) {
            defer wg.Done()

            endpoint := fmt.Sprintf("http://%s/topics", addr)
            c.logf("CI: querying nsqlookupd %s", endpoint)

            var resp respType
            err := c.client.NegotiateV1(endpoint, &resp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            lock.Lock()
            defer lock.Unlock()
            topics = append(topics, resp.Topics...)
        }(addr)
    }
    wg.Wait()

    if len(errs) == len(lookupdHTTPAddrs) {
        return nil, fmt.Errorf("Failed to query any nsqlookupd: %s", ErrList(errs))
    }

    topics = stringy.Uniq(topics)
    sort.Strings(topics)

    if len(errs) > 0 {
        return topics, ErrList(errs)
    }
    return topics, nil
}

// GetLookupdTopicChannels returns a []string containing a union of all the channels
// from all the given lookupd for the given topic
func (c *ClusterInfo) GetLookupdTopicChannels(topic string, lookupdHTTPAddrs []string) ([]string, error) {
    var channels []string
    var lock sync.Mutex
    var wg sync.WaitGroup
    var errs []error

    type respType struct {
        Channels []string `json:"channels"`
    }

    for _, addr := range lookupdHTTPAddrs {
        wg.Add(1)
        go func(addr string) {
            defer wg.Done()

            endpoint := fmt.Sprintf("http://%s/channels?topic=%s", addr, url.QueryEscape(topic))
            c.logf("CI: querying nsqlookupd %s", endpoint)

            var resp respType
            err := c.client.NegotiateV1(endpoint, &resp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            lock.Lock()
            defer lock.Unlock()
            channels = append(channels, resp.Channels...)
        }(addr)
    }
    wg.Wait()

    if len(errs) == len(lookupdHTTPAddrs) {
        return nil, fmt.Errorf("Failed to query any nsqlookupd: %s", ErrList(errs))
    }

    channels = stringy.Uniq(channels)
    sort.Strings(channels)

    if len(errs) > 0 {
        return channels, ErrList(errs)
    }
    return channels, nil
}

// GetLookupdProducers returns Producers of all the nsqd connected to the given lookupds
func (c *ClusterInfo) GetLookupdProducers(lookupdHTTPAddrs []string) (Producers, error) {
    var producers []*Producer
    var lock sync.Mutex
    var wg sync.WaitGroup
    var errs []error

    producersByAddr := make(map[string]*Producer)
    maxVersion, _ := semver.Parse("0.0.0")

    type respType struct {
        Producers []*Producer `json:"producers"`
    }

    for _, addr := range lookupdHTTPAddrs {
        wg.Add(1)
        go func(addr string) {
            defer wg.Done()

            endpoint := fmt.Sprintf("http://%s/nodes", addr)
            c.logf("CI: querying nsqlookupd %s", endpoint)

            var resp respType
            err := c.client.NegotiateV1(endpoint, &resp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            lock.Lock()
            defer lock.Unlock()
            for _, producer := range resp.Producers {
                key := producer.TCPAddress()
                p, ok := producersByAddr[key]
                if !ok {
                    producersByAddr[key] = producer
                    producers = append(producers, producer)
                    if maxVersion.LT(producer.VersionObj) {
                        maxVersion = producer.VersionObj
                    }
                    sort.Sort(producer.Topics)
                    p = producer
                }
                p.RemoteAddresses = append(p.RemoteAddresses,
                    fmt.Sprintf("%s/%s", addr, producer.Address()))
            }
        }(addr)
    }
    wg.Wait()

    if len(errs) == len(lookupdHTTPAddrs) {
        return nil, fmt.Errorf("Failed to query any nsqlookupd: %s", ErrList(errs))
    }

    for _, producer := range producersByAddr {
        if producer.VersionObj.LT(maxVersion) {
            producer.OutOfDate = true
        }
    }
    sort.Sort(ProducersByHost{producers})

    if len(errs) > 0 {
        return producers, ErrList(errs)
    }
    return producers, nil
}

// GetLookupdTopicProducers returns Producers of all the nsqd for a given topic by
// unioning the nodes returned from the given lookupd
func (c *ClusterInfo) GetLookupdTopicProducers(topic string, lookupdHTTPAddrs []string) (Producers, error) {
    var producers Producers
    var lock sync.Mutex
    var wg sync.WaitGroup
    var errs []error

    type respType struct {
        Producers Producers `json:"producers"`
    }

    for _, addr := range lookupdHTTPAddrs {
        wg.Add(1)
        go func(addr string) {
            defer wg.Done()

            endpoint := fmt.Sprintf("http://%s/lookup?topic=%s", addr, url.QueryEscape(topic))
            c.logf("CI: querying nsqlookupd %s", endpoint)

            var resp respType
            err := c.client.NegotiateV1(endpoint, &resp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            lock.Lock()
            defer lock.Unlock()
            for _, p := range resp.Producers {
                for _, pp := range producers {
                    if p.HTTPAddress() == pp.HTTPAddress() {
                        goto skip
                    }
                }
                producers = append(producers, p)
            skip:
            }
        }(addr)
    }
    wg.Wait()

    if len(errs) == len(lookupdHTTPAddrs) {
        return nil, fmt.Errorf("Failed to query any nsqlookupd: %s", ErrList(errs))
    }
    if len(errs) > 0 {
        return producers, ErrList(errs)
    }
    return producers, nil
}

// GetNSQDTopics returns a []string containing all the topics produced by the given nsqd
func (c *ClusterInfo) GetNSQDTopics(nsqdHTTPAddrs []string) ([]string, error) {
    var topics []string
    var lock sync.Mutex
    var wg sync.WaitGroup
    var errs []error

    type respType struct {
        Topics []struct {
            Name string `json:"topic_name"`
        } `json:"topics"`
    }

    for _, addr := range nsqdHTTPAddrs {
        wg.Add(1)
        go func(addr string) {
            defer wg.Done()

            endpoint := fmt.Sprintf("http://%s/stats?format=json", addr)
            c.logf("CI: querying nsqd %s", endpoint)

            var resp respType
            err := c.client.NegotiateV1(endpoint, &resp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            lock.Lock()
            defer lock.Unlock()
            for _, topic := range resp.Topics {
                topics = stringy.Add(topics, topic.Name)
            }
        }(addr)
    }
    wg.Wait()

    if len(errs) == len(nsqdHTTPAddrs) {
        return nil, fmt.Errorf("Failed to query any nsqd: %s", ErrList(errs))
    }

    sort.Strings(topics)

    if len(errs) > 0 {
        return topics, ErrList(errs)
    }
    return topics, nil
}

// GetNSQDProducers returns Producers of all the given nsqd
func (c *ClusterInfo) GetNSQDProducers(nsqdHTTPAddrs []string) (Producers, error) {
    var producers Producers
    var lock sync.Mutex
    var wg sync.WaitGroup
    var errs []error

    type infoRespType struct {
        Version          string `json:"version"`
        BroadcastAddress string `json:"broadcast_address"`
        Hostname         string `json:"hostname"`
        HTTPPort         int    `json:"http_port"`
        TCPPort          int    `json:"tcp_port"`
    }

    type statsRespType struct {
        Topics []struct {
            Name string `json:"topic_name"`
        } `json:"topics"`
    }

    for _, addr := range nsqdHTTPAddrs {
        wg.Add(1)
        go func(addr string) {
            defer wg.Done()

            endpoint := fmt.Sprintf("http://%s/info", addr)
            c.logf("CI: querying nsqd %s", endpoint)

            var infoResp infoRespType
            err := c.client.NegotiateV1(endpoint, &infoResp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            endpoint = fmt.Sprintf("http://%s/stats?format=json", addr)
            c.logf("CI: querying nsqd %s", endpoint)

            var statsResp statsRespType
            err = c.client.NegotiateV1(endpoint, &statsResp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            var producerTopics ProducerTopics
            for _, t := range statsResp.Topics {
                producerTopics = append(producerTopics, ProducerTopic{Topic: t.Name})
            }

            version, err := semver.Parse(infoResp.Version)
            if err != nil {
                version, _ = semver.Parse("0.0.0")
            }

            lock.Lock()
            defer lock.Unlock()
            producers = append(producers, &Producer{
                Version:          infoResp.Version,
                VersionObj:       version,
                BroadcastAddress: infoResp.BroadcastAddress,
                Hostname:         infoResp.Hostname,
                HTTPPort:         infoResp.HTTPPort,
                TCPPort:          infoResp.TCPPort,
                Topics:           producerTopics,
            })
        }(addr)
    }
    wg.Wait()

    if len(errs) == len(nsqdHTTPAddrs) {
        return nil, fmt.Errorf("Failed to query any nsqd: %s", ErrList(errs))
    }
    if len(errs) > 0 {
        return producers, ErrList(errs)
    }
    return producers, nil
}

// GetNSQDTopicProducers returns Producers containing the addresses of all the nsqd
// that produce the given topic
func (c *ClusterInfo) GetNSQDTopicProducers(topic string, nsqdHTTPAddrs []string) (Producers, error) {
    var producers Producers
    var lock sync.Mutex
    var wg sync.WaitGroup
    var errs []error

    type infoRespType struct {
        Version          string `json:"version"`
        BroadcastAddress string `json:"broadcast_address"`
        Hostname         string `json:"hostname"`
        HTTPPort         int    `json:"http_port"`
        TCPPort          int    `json:"tcp_port"`
    }

    type statsRespType struct {
        Topics []struct {
            Name string `json:"topic_name"`
        } `json:"topics"`
    }

    for _, addr := range nsqdHTTPAddrs {
        wg.Add(1)
        go func(addr string) {
            defer wg.Done()

            endpoint := fmt.Sprintf("http://%s/stats?format=json", addr)
            c.logf("CI: querying nsqd %s", endpoint)

            var statsResp statsRespType
            err := c.client.NegotiateV1(endpoint, &statsResp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            var producerTopics ProducerTopics
            for _, t := range statsResp.Topics {
                producerTopics = append(producerTopics, ProducerTopic{Topic: t.Name})
            }

            for _, t := range statsResp.Topics {
                if t.Name == topic {
                    endpoint := fmt.Sprintf("http://%s/info", addr)
                    c.logf("CI: querying nsqd %s", endpoint)

                    var infoResp infoRespType
                    err := c.client.NegotiateV1(endpoint, &infoResp)
                    if err != nil {
                        lock.Lock()
                        errs = append(errs, err)
                        lock.Unlock()
                        return
                    }

                    version, err := semver.Parse(infoResp.Version)
                    if err != nil {
                        version, _ = semver.Parse("0.0.0")
                    }

                    // if BroadcastAddress/HTTPPort are missing, use the values from `addr` for
                    // backwards compatibility

                    if infoResp.BroadcastAddress == "" {
                        var p string
                        infoResp.BroadcastAddress, p, _ = net.SplitHostPort(addr)
                        infoResp.HTTPPort, _ = strconv.Atoi(p)
                    }
                    if infoResp.Hostname == "" {
                        infoResp.Hostname, _, _ = net.SplitHostPort(addr)
                    }

                    lock.Lock()
                    producers = append(producers, &Producer{
                        Version:          infoResp.Version,
                        VersionObj:       version,
                        BroadcastAddress: infoResp.BroadcastAddress,
                        Hostname:         infoResp.Hostname,
                        HTTPPort:         infoResp.HTTPPort,
                        TCPPort:          infoResp.TCPPort,
                        Topics:           producerTopics,
                    })
                    lock.Unlock()

                    return
                }
            }
        }(addr)
    }
    wg.Wait()

    if len(errs) == len(nsqdHTTPAddrs) {
        return nil, fmt.Errorf("Failed to query any nsqd: %s", ErrList(errs))
    }
    if len(errs) > 0 {
        return producers, ErrList(errs)
    }
    return producers, nil
}

// GetNSQDStats returns aggregate topic and channel stats from the given Producers
//
// if selectedTopic is empty, this will return stats for *all* topic/channels
// and the ChannelStats dict will be keyed by topic + ':' + channel
func (c *ClusterInfo) GetNSQDStats(producers Producers, selectedTopic string) ([]*TopicStats, map[string]*ChannelStats, error) {
    var lock sync.Mutex
    var wg sync.WaitGroup
    var topicStatsList TopicStatsList
    var errs []error

    channelStatsMap := make(map[string]*ChannelStats)

    type respType struct {
        Topics []*TopicStats `json:"topics"`
    }

    for _, p := range producers {
        wg.Add(1)
        go func(p *Producer) {
            defer wg.Done()

            addr := p.HTTPAddress()
            endpoint := fmt.Sprintf("http://%s/stats?format=json", addr)
            c.logf("CI: querying nsqd %s", endpoint)

            var resp respType
            err := c.client.NegotiateV1(endpoint, &resp)
            if err != nil {
                lock.Lock()
                errs = append(errs, err)
                lock.Unlock()
                return
            }

            lock.Lock()
            defer lock.Unlock()
            for _, topic := range resp.Topics {
                topic.Node = addr
                topic.Hostname = p.Hostname
                topic.MemoryDepth = topic.Depth - topic.BackendDepth
                if selectedTopic != "" && topic.TopicName != selectedTopic {
                    continue
                }
                topicStatsList = append(topicStatsList, topic)

                for _, channel := range topic.Channels {
                    channel.Node = addr
                    channel.Hostname = p.Hostname
                    channel.TopicName = topic.TopicName
                    channel.MemoryDepth = channel.Depth - channel.BackendDepth
                    key := channel.ChannelName
                    if selectedTopic == "" {
                        key = fmt.Sprintf("%s:%s", topic.TopicName, channel.ChannelName)
                    }
                    channelStats, ok := channelStatsMap[key]
                    if !ok {
                        channelStats = &ChannelStats{
                            Node:        addr,
                            TopicName:   topic.TopicName,
                            ChannelName: channel.ChannelName,
                        }
                        channelStatsMap[key] = channelStats
                    }
                    for _, c := range channel.Clients {
                        c.Node = addr
                    }
                    channelStats.Add(channel)
                }
            }
        }(p)
    }
    wg.Wait()

    if len(errs) == len(producers) {
        return nil, nil, fmt.Errorf("Failed to query any nsqd: %s", ErrList(errs))
    }

    sort.Sort(TopicStatsByHost{topicStatsList})

    if len(errs) > 0 {
        return topicStatsList, channelStatsMap, ErrList(errs)
    }
    return topicStatsList, channelStatsMap, nil
}

// TombstoneNodeForTopic tombstones the given node for the given topic on all the given nsqlookupd
// and deletes the topic from the node
func (c *ClusterInfo) TombstoneNodeForTopic(topic string, node string, lookupdHTTPAddrs []string) error {
    var errs []error

    // tombstone the topic on all the lookupds
    qs := fmt.Sprintf("topic=%s&node=%s", url.QueryEscape(topic), url.QueryEscape(node))
    err := c.versionPivotNSQLookupd(lookupdHTTPAddrs, "tombstone_topic_producer", "topic/tombstone", qs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    producers, err := c.GetNSQDProducers([]string{node})
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    // delete the topic on the producer
    qs = fmt.Sprintf("topic=%s", url.QueryEscape(topic))
    err = c.versionPivotProducers(producers, "delete_topic", "topic/delete", qs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    if len(errs) > 0 {
        return ErrList(errs)
    }
    return nil
}

func (c *ClusterInfo) CreateTopicChannel(topicName string, channelName string, lookupdHTTPAddrs []string) error {
    var errs []error

    // create the topic on all the nsqlookupd
    qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
    err := c.versionPivotNSQLookupd(lookupdHTTPAddrs, "create_topic", "topic/create", qs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    if len(channelName) > 0 {
        qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))

        // create the channel on all the nsqlookupd
        err := c.versionPivotNSQLookupd(lookupdHTTPAddrs, "create_channel", "channel/create", qs)
        if err != nil {
            pe, ok := err.(PartialErr)
            if !ok {
                return err
            }
            errs = append(errs, pe.Errors()...)
        }

        // create the channel on all the nsqd that produce the topic
        producers, err := c.GetLookupdTopicProducers(topicName, lookupdHTTPAddrs)
        if err != nil {
            pe, ok := err.(PartialErr)
            if !ok {
                return err
            }
            errs = append(errs, pe.Errors()...)
        }
        err = c.versionPivotProducers(producers, "create_channel", "channel/create", qs)
        if err != nil {
            pe, ok := err.(PartialErr)
            if !ok {
                return err
            }
            errs = append(errs, pe.Errors()...)
        }
    }

    if len(errs) > 0 {
        return ErrList(errs)
    }
    return nil
}

func (c *ClusterInfo) DeleteTopic(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
    var errs []error

    // for topic removal, you need to get all the producers _first_
    producers, err := c.GetTopicProducers(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))

    // remove the topic from all the nsqlookupd
    err = c.versionPivotNSQLookupd(lookupdHTTPAddrs, "delete_topic", "topic/delete", qs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    // remove the topic from all the nsqd that produce this topic
    err = c.versionPivotProducers(producers, "delete_topic", "topic/delete", qs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    if len(errs) > 0 {
        return ErrList(errs)
    }
    return nil
}

func (c *ClusterInfo) DeleteChannel(topicName string, channelName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
    var errs []error

    producers, err := c.GetTopicProducers(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))

    // remove the channel from all the nsqlookupd
    err = c.versionPivotNSQLookupd(lookupdHTTPAddrs, "delete_channel", "channel/delete", qs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    // remove the channel from all the nsqd that produce this topic
    err = c.versionPivotProducers(producers, "delete_channel", "channel/delete", qs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    if len(errs) > 0 {
        return ErrList(errs)
    }
    return nil
}

func (c *ClusterInfo) PauseTopic(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
    qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
    return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "pause_topic", "topic/pause", qs)
}

func (c *ClusterInfo) UnPauseTopic(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
    qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
    return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "unpause_topic", "topic/unpause", qs)
}

func (c *ClusterInfo) PauseChannel(topicName string, channelName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
    qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))
    return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "pause_channel", "channel/pause", qs)
}

func (c *ClusterInfo) UnPauseChannel(topicName string, channelName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
    qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))
    return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "unpause_channel", "channel/unpause", qs)
}

func (c *ClusterInfo) EmptyTopic(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
    qs := fmt.Sprintf("topic=%s", url.QueryEscape(topicName))
    return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "empty_topic", "topic/empty", qs)
}

func (c *ClusterInfo) EmptyChannel(topicName string, channelName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) error {
    qs := fmt.Sprintf("topic=%s&channel=%s", url.QueryEscape(topicName), url.QueryEscape(channelName))
    return c.actionHelper(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs, "empty_channel", "channel/empty", qs)
}

func (c *ClusterInfo) actionHelper(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string, deprecatedURI string, v1URI string, qs string) error {
    var errs []error

    producers, err := c.GetTopicProducers(topicName, lookupdHTTPAddrs, nsqdHTTPAddrs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    err = c.versionPivotProducers(producers, deprecatedURI, v1URI, qs)
    if err != nil {
        pe, ok := err.(PartialErr)
        if !ok {
            return err
        }
        errs = append(errs, pe.Errors()...)
    }

    if len(errs) > 0 {
        return ErrList(errs)
    }
    return nil
}

func (c *ClusterInfo) GetProducers(lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) (Producers, error) {
    if len(lookupdHTTPAddrs) != 0 {
        return c.GetLookupdProducers(lookupdHTTPAddrs)
    }
    return c.GetNSQDProducers(nsqdHTTPAddrs)
}

func (c *ClusterInfo) GetTopicProducers(topicName string, lookupdHTTPAddrs []string, nsqdHTTPAddrs []string) (Producers, error) {
    if len(lookupdHTTPAddrs) != 0 {
        return c.GetLookupdTopicProducers(topicName, lookupdHTTPAddrs)
    }
    return c.GetNSQDTopicProducers(topicName, nsqdHTTPAddrs)
}

func (c *ClusterInfo) versionPivotNSQLookupd(addrs []string, deprecatedURI string, v1URI string, qs string) error {
    var errs []error

    for _, addr := range addrs {
        nodeVer, _ := c.GetVersion(addr)

        uri := deprecatedURI
        if nodeVer.NE(semver.Version{}) && nodeVer.GTE(v1EndpointVersion) {
            uri = v1URI
        }

        endpoint := fmt.Sprintf("http://%s/%s?%s", addr, uri, qs)
        c.logf("CI: querying nsqlookupd %s", endpoint)
        err := c.client.POSTV1(endpoint)
        if err != nil {
            errs = append(errs, err)
            continue
        }
    }

    if len(errs) > 0 {
        return ErrList(errs)
    }
    return nil
}

func (c *ClusterInfo) versionPivotProducers(pl Producers, deprecatedURI string, v1URI string, qs string) error {
    var errs []error

    for _, p := range pl {
        uri := deprecatedURI
        if p.VersionObj.NE(semver.Version{}) && p.VersionObj.GTE(v1EndpointVersion) {
            uri = v1URI
        }

        endpoint := fmt.Sprintf("http://%s/%s?%s", p.HTTPAddress(), uri, qs)
        c.logf("CI: querying nsqd %s", endpoint)
        err := c.client.POSTV1(endpoint)
        if err != nil {
            errs = append(errs, err)
            continue
        }
    }

    if len(errs) > 0 {
        return ErrList(errs)
    }
    return nil
}

data.go的更多相关文章

  1. CYQ.Data、ASP.NET Aries 百家企业使用名单

    如果您或您所在的公司正在使用此框架,请联系左侧的扣扣,告知我信息,我将为您添加链接: 以下内容为已反馈的用户,(收集始于:2016-08-08),仅展示99家: 序号 企业名称 企业网址 备注 1 山 ...

  2. 终于等到你:CYQ.Data V5系列 (ORM数据层)最新版本开源了

    前言: 不要问我框架为什么从收费授权转到免费开源,人生没有那么多为什么,这些年我开源的东西并不少,虽然这个是最核心的,看淡了就也没什么了. 群里的网友:太平说: 记得一年前你开源另一个项目的时候我就说 ...

  3. CodeSimth - .Net Framework Data Provider 可能没有安装。解决方法

    今天想使用CodeSimth生成一个sqlite数据库的模板.当添加添加数据库的时候发现: .Net Framework Data Provider 可能没有安装. 下面找到官方的文档说明: SQLi ...

  4. Oracle Database 12c Data Redaction介绍

    什么是Data Redaction Data Redaction是Oracle Database 12c的高级安全选项之中的一个新功能,Oracle中国在介绍这个功能的时候,翻译为“数据编纂”,在EM ...

  5. 快速搭建springmvc+spring data jpa工程

    一.前言 这里简单讲述一下如何快速使用springmvc和spring data jpa搭建后台开发工程,并提供了一个简单的demo作为参考. 二.创建maven工程 http://www.cnblo ...

  6. 【Big Data】HADOOP集群的配置(一)

    Hadoop集群的配置(一) 摘要: hadoop集群配置系列文档,是笔者在实验室真机环境实验后整理而得.以便随后工作所需,做以知识整理,另则与博客园朋友分享实验成果,因为笔者在学习初期,也遇到不少问 ...

  7. 代码的坏味道(16)——纯稚的数据类(Data Class)

    坏味道--纯稚的数据类(Data Class) 特征 纯稚的数据类(Data Class) 指的是只包含字段和访问它们的getter和setter函数的类.这些仅仅是供其他类使用的数据容器.这些类不包 ...

  8. R abalone data set

    #鲍鱼数据集aburl <- 'http://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data' ab ...

  9. java.IO输入输出流:过滤流:buffer流和data流

    java.io使用了适配器模式装饰模式等设计模式来解决字符流的套接和输入输出问题. 字节流只能一次处理一个字节,为了更方便的操作数据,便加入了套接流. 问题引入:缓冲流为什么比普通的文件字节流效率高? ...

  10. 怎样两个月完成Udacity Data Analyst Nanodegree

    在迷恋数据科学很久后,我决定要在MOOC网站上拿到一份Data Science的证书.美国三个MOOC网站,Udacity上的课程已经被分成了数个nanodegree,每个nanodegree都是目前 ...

随机推荐

  1. Java Socket:Java-NIO-Selector

    Selector 的出现,大大改善了多个 Java Socket的效率.在没有NIO的时候,轮询多个socket是通过read阻塞来完成,即使是非阻塞模式,我们在轮询socket是否就绪的时候依然需要 ...

  2. java线程的同步控制--重入锁ReentrantLock

    我们常用的synchronized关键字是一种最简单的线程同步控制方法,它决定了一个线程是否可以访问临界区资源.同时Object.wait() 和Object.notify()方法起到了线程等待和通知 ...

  3. WebService学习--(二)webservice相关介绍

    一.WebService是什么? 1. 基于Web的服务:服务器端整出一些资源让客户端应用访问(获取数据) 2. 一个跨语言.跨平台的规范(抽象) 3. 多个跨平台.跨语言的应用间通信整合的方案(实际 ...

  4. 关于Intent

    TCP/IP 协议是Internet国际网络的基础,主要包括TCP,IP,UDP和ICMP等协议 要连上internet,操作系统必须安装TCP/IP协议,TCP/IP协议可以让不同类型,不同操作系统 ...

  5. 经典栈溢出之MS060-040漏洞分析

    找了好久才找到Win 2000 NetApi32.dll样本,下面我对这个经典栈溢出进行一下分析,使用IDA打开NetApi32.dll,问题函数:NetpwPathCanonucalize.实验环境 ...

  6. 树的广度优先遍历和深度优先遍历(递归非递归、Java实现)

    在编程生活中,我们总会遇见树性结构,这几天刚好需要对树形结构操作,就记录下自己的操作方式以及过程.现在假设有一颗这样树,(是不是二叉树都没关系,原理都是一样的) 1.广度优先遍历 英文缩写为BFS即B ...

  7. 解密for循环工作机制之迭代器,以及生成器、三元表达式与列表解析、解压序列

    本节内容 1.迭代器协议与for循环 2.三元表达式 3.解压序列 4.列表解析 5.生成器 迭代器协议与for循环 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中下一项, ...

  8. 电商网站开发记录(三) Spring的引入,以及配置详解

    1.web.xml配置注解<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi=& ...

  9. DDD实战进阶第一波(九):开发一般业务的大健康行业直销系统(实现经销商上下文仓储与领域逻辑)

    上篇文章主要讲述了经销商上下文的需求与POCO对象,这篇文章主要讲述该界限上下文的仓储与领域逻辑的实现. 关于界限上下文与EF Core数据访问上下文参考产品上下文相应的实现,这里不再累述. 因为在经 ...

  10. Java虚拟机-类文件

    代码编译的结果从本地机器码转换为字节码,是存储格式发展的一小步,却是编程语言发展的一大步.计算机只认识0和1,所以我们的程序需要经过编译器翻译成由0和1组成的二进制格式才能由计算机执行.经过技术的发展 ...