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