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 }