1package metadata 2 3import ( 4 "fmt" 5 "net" 6 "regexp" 7 "strconv" 8 "strings" 9 10 "github.com/hashicorp/go-version" 11 "github.com/hashicorp/serf/serf" 12 13 "github.com/hashicorp/consul/agent/structs" 14) 15 16// Key is used in maps and for equality tests. A key is based on endpoints. 17type Key struct { 18 name string 19} 20 21// Equal compares two Key objects 22func (k *Key) Equal(x *Key) bool { 23 return k.name == x.name 24} 25 26// Server is used to return details of a consul server 27type Server struct { 28 Name string // <node>.<dc> 29 ShortName string // <node> 30 ID string 31 Datacenter string 32 Segment string 33 Port int 34 SegmentAddrs map[string]string 35 SegmentPorts map[string]int 36 WanJoinPort int 37 Bootstrap bool 38 Expect int 39 Build version.Version 40 Version int 41 RaftVersion int 42 Addr net.Addr 43 Status serf.MemberStatus 44 ReadReplica bool 45 ACLs structs.ACLMode 46 FeatureFlags map[string]int 47 48 // If true, use TLS when connecting to this server 49 UseTLS bool 50} 51 52// Key returns the corresponding Key 53func (s *Server) Key() *Key { 54 return &Key{ 55 name: s.Name, 56 } 57} 58 59// String returns a string representation of Server 60func (s *Server) String() string { 61 var addrStr, networkStr string 62 if s.Addr != nil { 63 addrStr = s.Addr.String() 64 networkStr = s.Addr.Network() 65 } 66 67 return fmt.Sprintf("%s (Addr: %s/%s) (DC: %s)", s.Name, networkStr, addrStr, s.Datacenter) 68} 69 70var versionFormat = regexp.MustCompile(`\d+\.\d+\.\d+`) 71 72// IsConsulServer returns true if a serf member is a consul server 73// agent. Returns a bool and a pointer to the Server. 74func IsConsulServer(m serf.Member) (bool, *Server) { 75 if m.Tags["role"] != "consul" { 76 return false, nil 77 } 78 79 datacenter := m.Tags["dc"] 80 segment := m.Tags["segment"] 81 _, bootstrap := m.Tags["bootstrap"] 82 _, useTLS := m.Tags["use_tls"] 83 84 expect := 0 85 expectStr, ok := m.Tags["expect"] 86 var err error 87 if ok { 88 expect, err = strconv.Atoi(expectStr) 89 if err != nil { 90 return false, nil 91 } 92 } 93 94 portStr := m.Tags["port"] 95 port, err := strconv.Atoi(portStr) 96 if err != nil { 97 return false, nil 98 } 99 100 var acls structs.ACLMode 101 if aclMode, ok := m.Tags["acls"]; ok { 102 acls = structs.ACLMode(aclMode) 103 } else { 104 acls = structs.ACLModeUnknown 105 } 106 107 segmentAddrs := make(map[string]string) 108 segmentPorts := make(map[string]int) 109 featureFlags := make(map[string]int) 110 for name, value := range m.Tags { 111 if strings.HasPrefix(name, "sl_") { 112 addr, port, err := net.SplitHostPort(value) 113 if err != nil { 114 return false, nil 115 } 116 segmentPort, err := strconv.Atoi(port) 117 if err != nil { 118 return false, nil 119 } 120 121 segmentName := strings.TrimPrefix(name, "sl_") 122 segmentAddrs[segmentName] = addr 123 segmentPorts[segmentName] = segmentPort 124 } else if strings.HasPrefix(name, featureFlagPrefix) { 125 featureName := strings.TrimPrefix(name, featureFlagPrefix) 126 featureState, err := strconv.Atoi(value) 127 if err != nil { 128 return false, nil 129 } 130 featureFlags[featureName] = featureState 131 } 132 } 133 134 buildVersion, err := Build(&m) 135 if err != nil { 136 return false, nil 137 } 138 139 wanJoinPort := 0 140 wanJoinPortStr, ok := m.Tags["wan_join_port"] 141 if ok { 142 wanJoinPort, err = strconv.Atoi(wanJoinPortStr) 143 if err != nil { 144 return false, nil 145 } 146 } 147 148 vsnStr := m.Tags["vsn"] 149 vsn, err := strconv.Atoi(vsnStr) 150 if err != nil { 151 return false, nil 152 } 153 154 raftVsn := 0 155 raftVsnStr, ok := m.Tags["raft_vsn"] 156 if ok { 157 raftVsn, err = strconv.Atoi(raftVsnStr) 158 if err != nil { 159 return false, nil 160 } 161 } 162 163 // Check if the server is a non voter 164 // DEPRECATED - remove looking for the nonvoter tag eventually once we don't have to support 165 // read replicas running v1.8.x and below. 166 _, nonVoter := m.Tags["nonvoter"] 167 _, readReplica := m.Tags["read_replica"] 168 169 addr := &net.TCPAddr{IP: m.Addr, Port: port} 170 171 parts := &Server{ 172 Name: m.Name, 173 ShortName: strings.TrimSuffix(m.Name, "."+datacenter), 174 ID: m.Tags["id"], 175 Datacenter: datacenter, 176 Segment: segment, 177 Port: port, 178 SegmentAddrs: segmentAddrs, 179 SegmentPorts: segmentPorts, 180 WanJoinPort: wanJoinPort, 181 Bootstrap: bootstrap, 182 Expect: expect, 183 Addr: addr, 184 Build: *buildVersion, 185 Version: vsn, 186 RaftVersion: raftVsn, 187 Status: m.Status, 188 UseTLS: useTLS, 189 // DEPRECATED - remove nonVoter check once support for that tag is removed 190 ReadReplica: nonVoter || readReplica, 191 ACLs: acls, 192 FeatureFlags: featureFlags, 193 } 194 return true, parts 195} 196 197const featureFlagPrefix = "ft_" 198 199// AddFeatureFlags to the tags. The tags map is expected to be a serf.Config.Tags. 200// The feature flags are encoded in the tags so that IsConsulServer can decode them 201// and populate the Server.FeatureFlags map. 202func AddFeatureFlags(tags map[string]string, flags ...string) { 203 for _, flag := range flags { 204 tags[featureFlagPrefix+flag] = "1" 205 } 206} 207