1package api
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"strconv"
8	"strings"
9	"time"
10)
11
12// AutopilotConfiguration is used for querying/setting the Autopilot configuration.
13// Autopilot helps manage operator tasks related to Consul servers like removing
14// failed servers from the Raft quorum.
15type AutopilotConfiguration struct {
16	// CleanupDeadServers controls whether to remove dead servers from the Raft
17	// peer list when a new server joins
18	CleanupDeadServers bool
19
20	// LastContactThreshold is the limit on the amount of time a server can go
21	// without leader contact before being considered unhealthy.
22	LastContactThreshold *ReadableDuration
23
24	// MaxTrailingLogs is the amount of entries in the Raft Log that a server can
25	// be behind before being considered unhealthy.
26	MaxTrailingLogs uint64
27
28	// MinQuorum sets the minimum number of servers allowed in a cluster before
29	// autopilot can prune dead servers.
30	MinQuorum uint
31
32	// ServerStabilizationTime is the minimum amount of time a server must be
33	// in a stable, healthy state before it can be added to the cluster. Only
34	// applicable with Raft protocol version 3 or higher.
35	ServerStabilizationTime *ReadableDuration
36
37	// (Enterprise-only) RedundancyZoneTag is the node tag to use for separating
38	// servers into zones for redundancy. If left blank, this feature will be disabled.
39	RedundancyZoneTag string
40
41	// (Enterprise-only) DisableUpgradeMigration will disable Autopilot's upgrade migration
42	// strategy of waiting until enough newer-versioned servers have been added to the
43	// cluster before promoting them to voters.
44	DisableUpgradeMigration bool
45
46	// (Enterprise-only) UpgradeVersionTag is the node tag to use for version info when
47	// performing upgrade migrations. If left blank, the Consul version will be used.
48	UpgradeVersionTag string
49
50	// CreateIndex holds the index corresponding the creation of this configuration.
51	// This is a read-only field.
52	CreateIndex uint64
53
54	// ModifyIndex will be set to the index of the last update when retrieving the
55	// Autopilot configuration. Resubmitting a configuration with
56	// AutopilotCASConfiguration will perform a check-and-set operation which ensures
57	// there hasn't been a subsequent update since the configuration was retrieved.
58	ModifyIndex uint64
59}
60
61// ServerHealth is the health (from the leader's point of view) of a server.
62type ServerHealth struct {
63	// ID is the raft ID of the server.
64	ID string
65
66	// Name is the node name of the server.
67	Name string
68
69	// Address is the address of the server.
70	Address string
71
72	// The status of the SerfHealth check for the server.
73	SerfStatus string
74
75	// Version is the Consul version of the server.
76	Version string
77
78	// Leader is whether this server is currently the leader.
79	Leader bool
80
81	// LastContact is the time since this node's last contact with the leader.
82	LastContact *ReadableDuration
83
84	// LastTerm is the highest leader term this server has a record of in its Raft log.
85	LastTerm uint64
86
87	// LastIndex is the last log index this server has a record of in its Raft log.
88	LastIndex uint64
89
90	// Healthy is whether or not the server is healthy according to the current
91	// Autopilot config.
92	Healthy bool
93
94	// Voter is whether this is a voting server.
95	Voter bool
96
97	// StableSince is the last time this server's Healthy value changed.
98	StableSince time.Time
99}
100
101// OperatorHealthReply is a representation of the overall health of the cluster
102type OperatorHealthReply struct {
103	// Healthy is true if all the servers in the cluster are healthy.
104	Healthy bool
105
106	// FailureTolerance is the number of healthy servers that could be lost without
107	// an outage occurring.
108	FailureTolerance int
109
110	// Servers holds the health of each server.
111	Servers []ServerHealth
112}
113
114// ReadableDuration is a duration type that is serialized to JSON in human readable format.
115type ReadableDuration time.Duration
116
117func NewReadableDuration(dur time.Duration) *ReadableDuration {
118	d := ReadableDuration(dur)
119	return &d
120}
121
122func (d *ReadableDuration) String() string {
123	return d.Duration().String()
124}
125
126func (d *ReadableDuration) Duration() time.Duration {
127	if d == nil {
128		return time.Duration(0)
129	}
130	return time.Duration(*d)
131}
132
133func (d *ReadableDuration) MarshalJSON() ([]byte, error) {
134	return []byte(fmt.Sprintf(`"%s"`, d.Duration().String())), nil
135}
136
137func (d *ReadableDuration) UnmarshalJSON(raw []byte) (err error) {
138	if d == nil {
139		return fmt.Errorf("cannot unmarshal to nil pointer")
140	}
141
142	var dur time.Duration
143	str := string(raw)
144	if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
145		// quoted string
146		dur, err = time.ParseDuration(str[1 : len(str)-1])
147		if err != nil {
148			return err
149		}
150	} else {
151		// no quotes, not a string
152		v, err := strconv.ParseFloat(str, 64)
153		if err != nil {
154			return err
155		}
156		dur = time.Duration(v)
157	}
158
159	*d = ReadableDuration(dur)
160	return nil
161}
162
163// AutopilotGetConfiguration is used to query the current Autopilot configuration.
164func (op *Operator) AutopilotGetConfiguration(q *QueryOptions) (*AutopilotConfiguration, error) {
165	r := op.c.newRequest("GET", "/v1/operator/autopilot/configuration")
166	r.setQueryOptions(q)
167	_, resp, err := requireOK(op.c.doRequest(r))
168	if err != nil {
169		return nil, err
170	}
171	defer resp.Body.Close()
172
173	var out AutopilotConfiguration
174	if err := decodeBody(resp, &out); err != nil {
175		return nil, err
176	}
177
178	return &out, nil
179}
180
181// AutopilotSetConfiguration is used to set the current Autopilot configuration.
182func (op *Operator) AutopilotSetConfiguration(conf *AutopilotConfiguration, q *WriteOptions) error {
183	r := op.c.newRequest("PUT", "/v1/operator/autopilot/configuration")
184	r.setWriteOptions(q)
185	r.obj = conf
186	_, resp, err := requireOK(op.c.doRequest(r))
187	if err != nil {
188		return err
189	}
190	resp.Body.Close()
191	return nil
192}
193
194// AutopilotCASConfiguration is used to perform a Check-And-Set update on the
195// Autopilot configuration. The ModifyIndex value will be respected. Returns
196// true on success or false on failures.
197func (op *Operator) AutopilotCASConfiguration(conf *AutopilotConfiguration, q *WriteOptions) (bool, error) {
198	r := op.c.newRequest("PUT", "/v1/operator/autopilot/configuration")
199	r.setWriteOptions(q)
200	r.params.Set("cas", strconv.FormatUint(conf.ModifyIndex, 10))
201	r.obj = conf
202	_, resp, err := requireOK(op.c.doRequest(r))
203	if err != nil {
204		return false, err
205	}
206	defer resp.Body.Close()
207
208	var buf bytes.Buffer
209	if _, err := io.Copy(&buf, resp.Body); err != nil {
210		return false, fmt.Errorf("Failed to read response: %v", err)
211	}
212	res := strings.Contains(buf.String(), "true")
213
214	return res, nil
215}
216
217// AutopilotServerHealth
218func (op *Operator) AutopilotServerHealth(q *QueryOptions) (*OperatorHealthReply, error) {
219	r := op.c.newRequest("GET", "/v1/operator/autopilot/health")
220	r.setQueryOptions(q)
221	_, resp, err := requireOK(op.c.doRequest(r))
222	if err != nil {
223		return nil, err
224	}
225	defer resp.Body.Close()
226
227	var out OperatorHealthReply
228	if err := decodeBody(resp, &out); err != nil {
229		return nil, err
230	}
231	return &out, nil
232}
233