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