github.com/thanos-io/thanos@v0.32.5/pkg/discovery/memcache/resolver.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package memcache
     5  
     6  import (
     7  	"bufio"
     8  	"context"
     9  	"fmt"
    10  	"net"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/thanos-io/thanos/pkg/runutil"
    16  )
    17  
    18  type clusterConfig struct {
    19  	version int
    20  	nodes   []node
    21  }
    22  
    23  type node struct {
    24  	dns  string
    25  	ip   string
    26  	port int
    27  }
    28  
    29  type Resolver interface {
    30  	Resolve(ctx context.Context, address string) (*clusterConfig, error)
    31  }
    32  
    33  type memcachedAutoDiscovery struct {
    34  	dialTimeout time.Duration
    35  }
    36  
    37  func (s *memcachedAutoDiscovery) Resolve(ctx context.Context, address string) (config *clusterConfig, err error) {
    38  	conn, err := net.DialTimeout("tcp", address, s.dialTimeout)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	defer runutil.CloseWithErrCapture(&err, conn, "closing connection")
    43  
    44  	rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
    45  	if _, err := fmt.Fprintf(rw, "config get cluster\n"); err != nil {
    46  		return nil, err
    47  	}
    48  	if err := rw.Flush(); err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	config, err = s.parseConfig(rw.Reader)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	return config, err
    58  }
    59  
    60  func (s *memcachedAutoDiscovery) parseConfig(reader *bufio.Reader) (*clusterConfig, error) {
    61  	clusterConfig := new(clusterConfig)
    62  
    63  	configMeta, err := reader.ReadString('\n')
    64  	if err != nil {
    65  		return nil, fmt.Errorf("failed to read config metadata: %s", err)
    66  	}
    67  	configMeta = strings.TrimSpace(configMeta)
    68  
    69  	// First line should be "CONFIG cluster 0 [length-of-payload-]
    70  	configMetaComponents := strings.Split(configMeta, " ")
    71  	if len(configMetaComponents) != 4 {
    72  		return nil, fmt.Errorf("expected 4 components in config metadata, and received %d, meta: %s", len(configMetaComponents), configMeta)
    73  	}
    74  
    75  	configSize, err := strconv.Atoi(configMetaComponents[3])
    76  	if err != nil {
    77  		return nil, fmt.Errorf("failed to parse config size from metadata: %s, error: %s", configMeta, err)
    78  	}
    79  
    80  	configVersion, err := reader.ReadString('\n')
    81  	if err != nil {
    82  		return nil, fmt.Errorf("failed to find config version: %s", err)
    83  	}
    84  	clusterConfig.version, err = strconv.Atoi(strings.TrimSpace(configVersion))
    85  	if err != nil {
    86  		return nil, fmt.Errorf("failed to parser config version: %s", err)
    87  	}
    88  
    89  	nodes, err := reader.ReadString('\n')
    90  	if err != nil {
    91  		return nil, fmt.Errorf("failed to read nodes: %s", err)
    92  	}
    93  
    94  	if len(configVersion)+len(nodes) != configSize {
    95  		return nil, fmt.Errorf("expected %d in config payload, but got %d instead.", configSize, len(configVersion)+len(nodes))
    96  	}
    97  
    98  	for _, host := range strings.Split(strings.TrimSpace(nodes), " ") {
    99  		dnsIpPort := strings.Split(host, "|")
   100  		if len(dnsIpPort) != 3 {
   101  			return nil, fmt.Errorf("node not in expected format: %s", dnsIpPort)
   102  		}
   103  		port, err := strconv.Atoi(dnsIpPort[2])
   104  		if err != nil {
   105  			return nil, fmt.Errorf("failed to parse port: %s, err: %s", dnsIpPort, err)
   106  		}
   107  		clusterConfig.nodes = append(clusterConfig.nodes, node{dns: dnsIpPort[0], ip: dnsIpPort[1], port: port})
   108  	}
   109  
   110  	return clusterConfig, nil
   111  }