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. Virtualbox开机启动,service命令管理

    #!/bin/bash#chkconfig:235 80 20#description:start or stop vbox#Author:Qty~20180502#OS:RedHatEnterpri ...

  2. insertion sort list (使用插入排序给链表排序)

    Sort a linked list using insertion sort. 对于数组的插入排序,可以参看排序算法入门之插入排序(java实现),遍历每个元素,然后相当于把每个元素插入到前面已经排 ...

  3. partition List(划分链表)

    Given a linked list and a value x, partition it such that all nodes less than x come before nodes gr ...

  4. Spring3 MVC

    一.前言: 大家好,Spring3 MVC是非常优秀的MVC框架,由其是在3.0版本发布后,现在有越来越多的团队选择了Spring3 MVC了.Spring3 MVC结构简单,应了那句话简单就是美,而 ...

  5. 多重影分身——C#中多线程的使用二(争抢共享资源)

    只要服务器承受得了,我们可以开任意个线程同时工作以提高效率,然而 两个线程争抢资源可能导致数据混乱. 例如: public class MyFood { public static int Last ...

  6. python 之路,200行Python代码写了个打飞机游戏!

    早就知道pygame模块,就是没怎么深入研究过,恰逢这周未没约到妹子,只能自己在家玩自己啦,一时兴起,花了几个小时写了个打飞机程序. 很有意思,跟大家分享下. 先看一下项目结构 "" ...

  7. python redis模块的常见的几个类 Redis 、StricRedis和ConnectionPool

    日常写代码过程中,经常需要连接redis进行操作.下面我就介绍下python操作redis模块redis中的几个常见类,包括redis连接池. 一.StrictRedis 类 请看代码:. #!/us ...

  8. Django1.6版本的PG数据库定义手动升级

    Django1.7以后添加了migration功能,数据库定义的升级完全实现自动化,之前是通过一个叫south的app来做的.这篇文章谈一下1.6下的手动更新升级. 1.table create和ta ...

  9. Spark学习笔记

    Map-Reduce 我认为上图代表着MapReduce不仅仅包括Map和Reduce两个步骤这么简单,还有两个隐含步骤没有明确,全部步骤包括:切片.转换.聚合.叠加,按照实际的运算场景上述步骤可以简 ...

  10. 开源纯C#工控网关+组态软件(十)移植到.NET Core

    一.   引子 写这个开源系列已经十来篇了.自从十年前注册博客园以来,关注了张善友.老赵.xiaotie.深蓝色右手等一众大牛,也围观了逗比的吉日嘎啦.精密顽石等形形色色的园友.然而整整十年一篇文章都 ...