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. javascript学习笔记(四) Number 数字类型

    数字格式化方法toFixed().toExponential().toPrecision(),三个方法都四舍五入 toFixed() 方法指定小数位个数  toExponential() 方法 用科学 ...

  2. 【.Net架构】BIM软件架构03:Web管控平台MVC架构

    一.前言        上一篇讲述的是将BIM平台后台架构CoreService.sln,该解决方案主要作用是对管控平台的核心业务进行封装,然后让前端的ApiController去调用该解决方案中的对 ...

  3. Colossus: Successor to the Google File System (GFS)

    Colossus is the successor to the Google File System (GFS) as mentioned in the recent paper on Spanne ...

  4. WebApi PUT、DELETE请求时出现405 - 不允许用于访问此页的 HTTP 谓词。

    开发时,新建WebApi项目需要用到Restful规范,此时请求有POST\PUT\DELETE\GET等请求 此时需要在web.config中加入 <system.webServer> ...

  5. java接受安卓及ios App上传的图片,并保存到阿里OSS

    做后台的时候,写了两个方法,分别用来获取安卓和苹果IOS端上传的头像,保存到阿里云OSS图片存储服务器上.(SMM框架) 安卓及H5版本: /** * 上传用户头像 */ @RequestMappin ...

  6. java-随机生成用户名(中文版及英文版)

    开发中遇到用户名随机生成的问题,总结了两个(中文版和英文版),相关方法在此,方便直接调用. 如下: //自动生成名字(中文) public static String getRandomJianHan ...

  7. tensorflow1.0.0 弃用了几个operator写法

    除法和取模运算符(/, //, %)现已匹配 Python(flooring)语义.这也适用于 tf.div 和 tf.mod.为了获取强制的基于整数截断的行为,你可以使用 tf.truncatedi ...

  8. Cython入门Demo(Linux)

    众所周知,Python语言是非常简单易用的,但是python程序在运行速度上还是有一些缺陷.于是,Cython就应运而生了,Cython作为Python的C扩展,保留了Python的语法特点,集成C语 ...

  9. 重温《STL源码剖析》笔记 第四章

    源码之前,了无秘密  ——侯杰 序列式容器 关联式容器 array(build in) RB-tree vector set heap   map priority-queue multiset li ...

  10. Effective C++ 读书笔记(46-50)

    条款四十六:需要类型转换时请为模板定义非成员函数 条款四十七:请使用traits classes 表现类型信息 1.整合重载技术后,traits classes 有可能在编译期对类型执行if...el ...