From 58f98db2275971261efb4c95d304a11aa8b33f66 Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Wed, 11 Nov 2020 13:19:02 -0500 Subject: [PATCH] Add a CLI command for retrieving the autopilot configuration. (#9142) --- agent/operator_endpoint_oss.go | 6 +- agent/operator_endpoint_test.go | 7 +- api/operator_autopilot.go | 4 +- command/commands_oss.go | 2 + command/operator/autopilot/state/formatter.go | 206 +++++++++ .../state/operator_autopilot_state.go | 99 +++++ .../state/operator_autopilot_state_test.go | 126 ++++++ .../state/testdata/enterprise/json.golden | 401 ++++++++++++++++++ .../state/testdata/enterprise/pretty.golden | 279 ++++++++++++ .../state/testdata/enterprise/state.json | 389 +++++++++++++++++ .../autopilot/state/testdata/oss/json.golden | 67 +++ .../state/testdata/oss/pretty.golden | 48 +++ .../autopilot/state/testdata/oss/state.json | 64 +++ website/pages/commands/operator/autopilot.mdx | 82 +++- 14 files changed, 1768 insertions(+), 12 deletions(-) create mode 100644 command/operator/autopilot/state/formatter.go create mode 100644 command/operator/autopilot/state/operator_autopilot_state.go create mode 100644 command/operator/autopilot/state/operator_autopilot_state_test.go create mode 100644 command/operator/autopilot/state/testdata/enterprise/json.golden create mode 100644 command/operator/autopilot/state/testdata/enterprise/pretty.golden create mode 100644 command/operator/autopilot/state/testdata/enterprise/state.json create mode 100644 command/operator/autopilot/state/testdata/oss/json.golden create mode 100644 command/operator/autopilot/state/testdata/oss/pretty.golden create mode 100644 command/operator/autopilot/state/testdata/oss/state.json diff --git a/agent/operator_endpoint_oss.go b/agent/operator_endpoint_oss.go index 9eef6d762..ff87332b6 100644 --- a/agent/operator_endpoint_oss.go +++ b/agent/operator_endpoint_oss.go @@ -11,6 +11,8 @@ func autopilotToAPIServerEnterprise(_ *autopilot.ServerState, _ *api.AutopilotSe // noop in oss } -func autopilotToAPIStateEnterprise(_ *autopilot.State, _ *api.AutopilotState) { - // noop in oss +func autopilotToAPIStateEnterprise(state *autopilot.State, apiState *api.AutopilotState) { + // without the enterprise features there is no different between these two and we don't want to + // alarm anyone by leaving this as the zero value. + apiState.OptimisticFailureTolerance = state.FailureTolerance } diff --git a/agent/operator_endpoint_test.go b/agent/operator_endpoint_test.go index 219be2c7a..0a398087c 100644 --- a/agent/operator_endpoint_test.go +++ b/agent/operator_endpoint_test.go @@ -656,9 +656,10 @@ func TestAutopilotStateToAPIConversion(t *testing.T) { } expected := api.AutopilotState{ - Healthy: true, - FailureTolerance: 1, - Leader: string(leaderID), + Healthy: true, + FailureTolerance: 1, + OptimisticFailureTolerance: 1, + Leader: string(leaderID), Voters: []string{ string(leaderID), string(follower1ID), diff --git a/api/operator_autopilot.go b/api/operator_autopilot.go index 8c42df6dd..57876ee9f 100644 --- a/api/operator_autopilot.go +++ b/api/operator_autopilot.go @@ -114,7 +114,7 @@ type OperatorHealthReply struct { type AutopilotState struct { Healthy bool FailureTolerance int - OptimisitcFailureTolerance int + OptimisticFailureTolerance int Servers map[string]AutopilotServer Leader string @@ -137,7 +137,7 @@ type AutopilotServer struct { StableSince time.Time RedundancyZone string `json:",omitempty"` UpgradeVersion string `json:",omitempty"` - ReadReplica bool `json:",omitempty"` + ReadReplica bool Status AutopilotServerStatus Meta map[string]string NodeType AutopilotServerType diff --git a/command/commands_oss.go b/command/commands_oss.go index 551e25952..d8e42f642 100644 --- a/command/commands_oss.go +++ b/command/commands_oss.go @@ -85,6 +85,7 @@ import ( operauto "github.com/hashicorp/consul/command/operator/autopilot" operautoget "github.com/hashicorp/consul/command/operator/autopilot/get" operautoset "github.com/hashicorp/consul/command/operator/autopilot/set" + operautostate "github.com/hashicorp/consul/command/operator/autopilot/state" operraft "github.com/hashicorp/consul/command/operator/raft" operraftlist "github.com/hashicorp/consul/command/operator/raft/listpeers" operraftremove "github.com/hashicorp/consul/command/operator/raft/removepeer" @@ -202,6 +203,7 @@ func init() { Register("operator autopilot", func(cli.Ui) (cli.Command, error) { return operauto.New(), nil }) Register("operator autopilot get-config", func(ui cli.Ui) (cli.Command, error) { return operautoget.New(ui), nil }) Register("operator autopilot set-config", func(ui cli.Ui) (cli.Command, error) { return operautoset.New(ui), nil }) + Register("operator autopilot state", func(ui cli.Ui) (cli.Command, error) { return operautostate.New(ui), nil }) Register("operator raft", func(cli.Ui) (cli.Command, error) { return operraft.New(), nil }) Register("operator raft list-peers", func(ui cli.Ui) (cli.Command, error) { return operraftlist.New(ui), nil }) Register("operator raft remove-peer", func(ui cli.Ui) (cli.Command, error) { return operraftremove.New(ui), nil }) diff --git a/command/operator/autopilot/state/formatter.go b/command/operator/autopilot/state/formatter.go new file mode 100644 index 000000000..cebc9e30d --- /dev/null +++ b/command/operator/autopilot/state/formatter.go @@ -0,0 +1,206 @@ +package state + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" + + "github.com/hashicorp/consul/api" +) + +const ( + PrettyFormat string = "pretty" + JSONFormat string = "json" +) + +// Formatter defines methods provided by an autopilot state output formatter +type Formatter interface { + FormatState(state *api.AutopilotState) (string, error) +} + +// GetSupportedFormats returns supported formats +func GetSupportedFormats() []string { + return []string{PrettyFormat, JSONFormat} +} + +// NewFormatter returns Formatter implementation +func NewFormatter(format string) (formatter Formatter, err error) { + switch format { + case PrettyFormat: + formatter = newPrettyFormatter() + case JSONFormat: + formatter = newJSONFormatter() + default: + err = fmt.Errorf("Unknown format: %s", format) + } + + return formatter, err +} + +func newPrettyFormatter() Formatter { + return &prettyFormatter{} +} + +type prettyFormatter struct { +} + +func outputStringSlice(buffer *bytes.Buffer, indent string, values []string) { + for _, val := range values { + buffer.WriteString(fmt.Sprintf("%s%s\n", indent, val)) + } +} + +type mapOutput struct { + key string + value string +} + +func formatZone(zoneName string, zone *api.AutopilotZone) string { + var buffer bytes.Buffer + + buffer.WriteString(fmt.Sprintf(" %s:\n", zoneName)) + buffer.WriteString(fmt.Sprintf(" Failure Tolerance: %d\n", zone.FailureTolerance)) + buffer.WriteString(" Voters:\n") + outputStringSlice(&buffer, " ", zone.Voters) + buffer.WriteString(" Servers:\n") + outputStringSlice(&buffer, " ", zone.Servers) + + return buffer.String() +} + +func formatServer(srv *api.AutopilotServer) string { + var buffer bytes.Buffer + + buffer.WriteString(fmt.Sprintf(" %s\n", srv.ID)) + buffer.WriteString(fmt.Sprintf(" Name: %s\n", srv.Name)) + buffer.WriteString(fmt.Sprintf(" Address: %s\n", srv.Address)) + buffer.WriteString(fmt.Sprintf(" Version: %s\n", srv.Version)) + buffer.WriteString(fmt.Sprintf(" Status: %s\n", srv.Status)) + buffer.WriteString(fmt.Sprintf(" Node Type: %s\n", srv.NodeType)) + buffer.WriteString(fmt.Sprintf(" Node Status: %s\n", srv.NodeStatus)) + buffer.WriteString(fmt.Sprintf(" Healthy: %t\n", srv.Healthy)) + buffer.WriteString(fmt.Sprintf(" Last Contact: %s\n", srv.LastContact.String())) + buffer.WriteString(fmt.Sprintf(" Last Term: %d\n", srv.LastTerm)) + buffer.WriteString(fmt.Sprintf(" Last Index: %d\n", srv.LastIndex)) + if srv.RedundancyZone != "" { + buffer.WriteString(fmt.Sprintf(" Redundancy Zone: %s\n", srv.RedundancyZone)) + } + if srv.UpgradeVersion != "" { + buffer.WriteString(fmt.Sprintf(" Upgrade Version: %s\n", srv.UpgradeVersion)) + } + if srv.ReadReplica { + buffer.WriteString(fmt.Sprintf(" Read Replica: %t\n", srv.ReadReplica)) + } + if len(srv.Meta) > 0 { + buffer.WriteString(fmt.Sprintf(" Meta\n")) + var outputs []mapOutput + for k, v := range srv.Meta { + outputs = append(outputs, mapOutput{key: k, value: fmt.Sprintf(" %q: %q\n", k, v)}) + } + + sort.Slice(outputs, func(i, j int) bool { + return outputs[i].key < outputs[j].key + }) + + for _, output := range outputs { + buffer.WriteString(output.value) + } + } + + return buffer.String() +} + +func (f *prettyFormatter) FormatState(state *api.AutopilotState) (string, error) { + + var buffer bytes.Buffer + + buffer.WriteString(fmt.Sprintf("Healthy: %t\n", state.Healthy)) + buffer.WriteString(fmt.Sprintf("Failure Tolerance: %d\n", state.FailureTolerance)) + buffer.WriteString(fmt.Sprintf("Optimistic Failure Tolerance: %d\n", state.OptimisticFailureTolerance)) + buffer.WriteString(fmt.Sprintf("Leader: %s\n", state.Leader)) + buffer.WriteString("Voters:\n") + outputStringSlice(&buffer, " ", state.Voters) + + if len(state.ReadReplicas) > 0 { + buffer.WriteString("Read Replicas:\n") + outputStringSlice(&buffer, " ", state.ReadReplicas) + } + + if len(state.RedundancyZones) > 0 { + var outputs []mapOutput + buffer.WriteString("Redundancy Zones:\n") + for zoneName, zone := range state.RedundancyZones { + outputs = append(outputs, mapOutput{key: zoneName, value: formatZone(zoneName, &zone)}) + } + sort.Slice(outputs, func(i, j int) bool { + return outputs[i].key < outputs[j].key + }) + + for _, output := range outputs { + buffer.WriteString(output.value) + } + } + + if state.Upgrade != nil { + u := state.Upgrade + buffer.WriteString("Upgrade:\n") + buffer.WriteString(fmt.Sprintf(" Status: %s\n", u.Status)) + buffer.WriteString(fmt.Sprintf(" Target Version: %s\n", u.TargetVersion)) + if len(u.TargetVersionVoters) > 0 { + buffer.WriteString(" Target Version Voters:\n") + outputStringSlice(&buffer, " ", u.TargetVersionVoters) + } + if len(u.TargetVersionNonVoters) > 0 { + buffer.WriteString(" Target Version Non-Voters:\n") + outputStringSlice(&buffer, " ", u.TargetVersionNonVoters) + } + if len(u.TargetVersionReadReplicas) > 0 { + buffer.WriteString(" Target Version ReadReplicas:\n") + outputStringSlice(&buffer, " ", u.TargetVersionReadReplicas) + } + if len(u.OtherVersionVoters) > 0 { + buffer.WriteString(" Other Version Voters:\n") + outputStringSlice(&buffer, " ", u.OtherVersionVoters) + } + if len(u.OtherVersionNonVoters) > 0 { + buffer.WriteString(" Other Version Non-Voters:\n") + outputStringSlice(&buffer, " ", u.OtherVersionNonVoters) + } + if len(u.OtherVersionReadReplicas) > 0 { + buffer.WriteString(" Other Version ReadReplicas:\n") + outputStringSlice(&buffer, " ", u.OtherVersionReadReplicas) + } + } + + buffer.WriteString("Servers:\n") + var outputs []mapOutput + for id, srv := range state.Servers { + outputs = append(outputs, mapOutput{key: id, value: formatServer(&srv)}) + } + + sort.Slice(outputs, func(i, j int) bool { + return outputs[i].key < outputs[j].key + }) + + for _, output := range outputs { + buffer.WriteString(output.value) + } + + return buffer.String(), nil +} + +func newJSONFormatter() Formatter { + return &jsonFormatter{} +} + +type jsonFormatter struct { +} + +func (f *jsonFormatter) FormatState(state *api.AutopilotState) (string, error) { + b, err := json.MarshalIndent(state, "", " ") + if err != nil { + return "", fmt.Errorf("Failed to marshal token: %v", err) + } + return string(b), nil +} diff --git a/command/operator/autopilot/state/operator_autopilot_state.go b/command/operator/autopilot/state/operator_autopilot_state.go new file mode 100644 index 000000000..cf4aebd74 --- /dev/null +++ b/command/operator/autopilot/state/operator_autopilot_state.go @@ -0,0 +1,99 @@ +package state + +import ( + "flag" + "fmt" + "strings" + + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/flags" + "github.com/mitchellh/cli" +) + +func New(ui cli.Ui) *cmd { + c := &cmd{UI: ui} + c.init() + return c +} + +type cmd struct { + UI cli.Ui + flags *flag.FlagSet + http *flags.HTTPFlags + help string + + format string +} + +func (c *cmd) init() { + c.flags = flag.NewFlagSet("", flag.ContinueOnError) + c.flags.StringVar( + &c.format, + "format", + PrettyFormat, + fmt.Sprintf("Output format {%s}", strings.Join(GetSupportedFormats(), "|")), + ) + c.http = &flags.HTTPFlags{} + flags.Merge(c.flags, c.http.ClientFlags()) + flags.Merge(c.flags, c.http.ServerFlags()) + c.help = flags.Usage(help, c.flags) +} + +func (c *cmd) Run(args []string) int { + if err := c.flags.Parse(args); err != nil { + if err == flag.ErrHelp { + return 0 + } + c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err)) + return 1 + } + + // Set up a client. + client, err := c.http.APIClient() + if err != nil { + c.UI.Error(fmt.Sprintf("Error initializing client: %s", err)) + return 1 + } + + // Fetch the current configuration. + opts := &api.QueryOptions{ + AllowStale: c.http.Stale(), + } + state, err := client.Operator().AutopilotState(opts) + if err != nil { + c.UI.Error(fmt.Sprintf("Error querying Autopilot state: %s", err)) + return 1 + } + + formatter, err := NewFormatter(c.format) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + + out, err := formatter.FormatState(state) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if out != "" { + c.UI.Info(out) + } + return 0 +} + +func (c *cmd) Synopsis() string { + return synopsis +} + +func (c *cmd) Help() string { + return c.help +} + +const synopsis = "Display the current Autopilot configuration" +const help = ` +Usage: consul operator autopilot get-config [options] + + Displays the current Autopilot configuration. +` diff --git a/command/operator/autopilot/state/operator_autopilot_state_test.go b/command/operator/autopilot/state/operator_autopilot_state_test.go new file mode 100644 index 000000000..e4208f218 --- /dev/null +++ b/command/operator/autopilot/state/operator_autopilot_state_test.go @@ -0,0 +1,126 @@ +package state + +import ( + "encoding/json" + "flag" + "io/ioutil" + "path/filepath" + "strings" + "testing" + + "github.com/hashicorp/consul/agent" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/testrpc" + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" +) + +// update allows golden files to be updated based on the current output. +var update = flag.Bool("update", false, "update golden files") + +// golden reads and optionally writes the expected data to the golden file, +// returning the contents as a string. +func golden(t *testing.T, name, got string) string { + t.Helper() + + golden := filepath.Join("testdata", name+".golden") + if *update && got != "" { + err := ioutil.WriteFile(golden, []byte(got), 0644) + require.NoError(t, err) + } + + expected, err := ioutil.ReadFile(golden) + require.NoError(t, err) + + return string(expected) +} + +func TestStateCommand_noTabs(t *testing.T) { + t.Parallel() + + if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { + t.Fatal("help has tabs") + } +} + +func TestStateCommand_Pretty(t *testing.T) { + t.Parallel() + a := agent.NewTestAgent(t, ` + node_id = "f0427127-7531-455a-b651-f1ea1d8451f0" + `) + + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + ui := cli.NewMockUi() + cmd := New(ui) + + args := []string{ + "-http-addr=" + a.HTTPAddr(), + } + + code := cmd.Run(args) + require.Empty(t, ui.ErrorWriter.String()) + require.Equal(t, code, 0) + output := ui.OutputWriter.String() + + // Just a few quick checks to ensure we got output + // the output formatter will be tested in another test. + require.Regexp(t, `^Healthy:`, output) + require.Regexp(t, `(?m)^Leader:`, output) +} + +func TestStateCommand_JSON(t *testing.T) { + t.Parallel() + a := agent.NewTestAgent(t, "") + + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + ui := cli.NewMockUi() + cmd := New(ui) + + args := []string{ + "-http-addr=" + a.HTTPAddr(), + "-format=json", + } + + code := cmd.Run(args) + require.Empty(t, ui.ErrorWriter.String()) + require.Equal(t, code, 0) + output := ui.OutputWriter.String() + + var state api.AutopilotState + require.NoError(t, json.Unmarshal([]byte(output), &state)) +} + +func TestStateCommand_Formatter(t *testing.T) { + cases := []string{ + "oss", + "enterprise", + } + + for _, name := range cases { + t.Run(name, func(t *testing.T) { + statePath := filepath.Join("testdata", name, "state.json") + input, err := ioutil.ReadFile(statePath) + require.NoError(t, err) + + var state api.AutopilotState + require.NoError(t, json.Unmarshal(input, &state)) + + for _, format := range GetSupportedFormats() { + t.Run(format, func(t *testing.T) { + formatter, err := NewFormatter(format) + require.NoError(t, err) + + actual, err := formatter.FormatState(&state) + require.NoError(t, err) + + expected := golden(t, filepath.Join(name, format), actual) + require.Equal(t, expected, actual) + }) + } + }) + } +} diff --git a/command/operator/autopilot/state/testdata/enterprise/json.golden b/command/operator/autopilot/state/testdata/enterprise/json.golden new file mode 100644 index 000000000..b7a839edb --- /dev/null +++ b/command/operator/autopilot/state/testdata/enterprise/json.golden @@ -0,0 +1,401 @@ +{ + "Healthy": true, + "FailureTolerance": 1, + "OptimisticFailureTolerance": 0, + "Servers": { + "1b8044f6-1d25-4a83-9662-acdf404341d2": { + "ID": "1b8044f6-1d25-4a83-9662-acdf404341d2", + "Name": "node6.new", + "Address": "198.18.1.6:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone3", + "UpgradeVersion": "2.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "2.0.0", + "zone": "zone3" + }, + "NodeType": "zone-standby" + }, + "1baeb453-ad9e-489a-bbfe-53bee097aec8": { + "ID": "1baeb453-ad9e-489a-bbfe-53bee097aec8", + "Name": "node4", + "Address": "198.18.0.4:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone2", + "UpgradeVersion": "1.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "1.0.0", + "zone": "zone2" + }, + "NodeType": "zone-standby" + }, + "3044d109-f028-4489-b59c-f267afe408f2": { + "ID": "3044d109-f028-4489-b59c-f267afe408f2", + "Name": "node2.new", + "Address": "198.18.1.2:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone1", + "UpgradeVersion": "2.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "2.0.0", + "zone": "zone1" + }, + "NodeType": "zone-standby" + }, + "4691e516-6989-4a16-8f55-12ff2226a3c9": { + "ID": "4691e516-6989-4a16-8f55-12ff2226a3c9", + "Name": "node4.new", + "Address": "198.18.1.4:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone2", + "UpgradeVersion": "2.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "2.0.0", + "zone": "zone2" + }, + "NodeType": "zone-standby" + }, + "4c42fe86-321d-4e8f-be0d-c261e6cfc453": { + "ID": "4c42fe86-321d-4e8f-be0d-c261e6cfc453", + "Name": "node5", + "Address": "198.18.0.5:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone3", + "UpgradeVersion": "1.0.0", + "ReadReplica": false, + "Status": "voter", + "Meta": { + "foo": "bar", + "upgrade": "1.0.0", + "zone": "zone3" + }, + "NodeType": "zone-voter" + }, + "6ca5a41c-6162-41c1-b4eb-09082efe206f": { + "ID": "6ca5a41c-6162-41c1-b4eb-09082efe206f", + "Name": "node2", + "Address": "198.18.0.2:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone1", + "UpgradeVersion": "1.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "1.0.0", + "zone": "zone1" + }, + "NodeType": "zone-standby" + }, + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d": { + "ID": "6e8a37e5-a1c4-4212-a75e-91487d8cda6d", + "Name": "node3", + "Address": "198.18.0.3:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone2", + "UpgradeVersion": "1.0.0", + "ReadReplica": false, + "Status": "voter", + "Meta": { + "foo": "bar", + "upgrade": "1.0.0", + "zone": "zone2" + }, + "NodeType": "zone-voter" + }, + "746b8782-ce77-41fd-bce0-cce62cca62b4": { + "ID": "746b8782-ce77-41fd-bce0-cce62cca62b4", + "Name": "node6", + "Address": "198.18.0.6:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone3", + "UpgradeVersion": "1.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "1.0.0", + "zone": "zone3" + }, + "NodeType": "zone-standby" + }, + "79324811-9588-4311-b208-f272e38aaabf": { + "ID": "79324811-9588-4311-b208-f272e38aaabf", + "Name": "node1", + "Address": "198.18.0.1:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "0s", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone1", + "UpgradeVersion": "1.0.0", + "ReadReplica": false, + "Status": "leader", + "Meta": { + "foo": "bar", + "upgrade": "1.0.0", + "zone": "zone1" + }, + "NodeType": "zone-voter" + }, + "7b02a615-ccce-4251-bda8-b89e0bd4f7c7": { + "ID": "7b02a615-ccce-4251-bda8-b89e0bd4f7c7", + "Name": "read-replica", + "Address": "198.18.0.7:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "2ms", + "LastTerm": 3, + "LastIndex": 39, + "Healthy": true, + "StableSince": "2020-11-06T14:53:00Z", + "UpgradeVersion": "1.0.0", + "ReadReplica": true, + "Status": "non-voter", + "Meta": { + "baz": "foo", + "version": "1.0.0" + }, + "NodeType": "read-replica" + }, + "98dfd0fd-504e-4280-8e73-6983a6af1b8c": { + "ID": "98dfd0fd-504e-4280-8e73-6983a6af1b8c", + "Name": "node5.new", + "Address": "198.18.1.5:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone3", + "UpgradeVersion": "2.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "foo": "bar", + "upgrade": "2.0.0", + "zone": "zone3" + }, + "NodeType": "zone-standby" + }, + "997b0851-37c5-4d65-a477-a8b3a56eea42": { + "ID": "997b0851-37c5-4d65-a477-a8b3a56eea42", + "Name": "node3.new", + "Address": "198.18.1.3:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone2", + "UpgradeVersion": "2.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "foo": "bar", + "upgrade": "2.0.0", + "zone": "zone2" + }, + "NodeType": "zone-standby" + }, + "de95799e-15a4-4c86-b508-78840554b7cb": { + "ID": "de95799e-15a4-4c86-b508-78840554b7cb", + "Name": "node1.new", + "Address": "198.18.1.1:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "0s", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone1", + "UpgradeVersion": "2.0.0", + "ReadReplica": false, + "Status": "non-voter", + "Meta": { + "foo": "bar", + "upgrade": "2.0.0", + "zone": "zone1" + }, + "NodeType": "zone-standby" + } + }, + "Leader": "79324811-9588-4311-b208-f272e38aaabf", + "Voters": [ + "79324811-9588-4311-b208-f272e38aaabf", + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d", + "4c42fe86-321d-4e8f-be0d-c261e6cfc453" + ], + "ReadReplicas": [ + "7b02a615-ccce-4251-bda8-b89e0bd4f7c7" + ], + "RedundancyZones": { + "zone1": { + "Servers": [ + "79324811-9588-4311-b208-f272e38aaabf", + "6ca5a41c-6162-41c1-b4eb-09082efe206f", + "de95799e-15a4-4c86-b508-78840554b7cb", + "3044d109-f028-4489-b59c-f267afe408f2" + ], + "Voters": [ + "79324811-9588-4311-b208-f272e38aaabf" + ], + "FailureTolerance": 3 + }, + "zone2": { + "Servers": [ + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d", + "1baeb453-ad9e-489a-bbfe-53bee097aec8", + "997b0851-37c5-4d65-a477-a8b3a56eea42", + "4691e516-6989-4a16-8f55-12ff2226a3c9" + ], + "Voters": [ + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d" + ], + "FailureTolerance": 3 + }, + "zone3": { + "Servers": [ + "4c42fe86-321d-4e8f-be0d-c261e6cfc453", + "746b8782-ce77-41fd-bce0-cce62cca62b4", + "98dfd0fd-504e-4280-8e73-6983a6af1b8c", + "1b8044f6-1d25-4a83-9662-acdf404341d2" + ], + "Voters": [ + "4c42fe86-321d-4e8f-be0d-c261e6cfc453" + ], + "FailureTolerance": 3 + } + }, + "Upgrade": { + "Status": "promoting", + "TargetVersion": "2.0.0", + "TargetVersionNonVoters": [ + "de95799e-15a4-4c86-b508-78840554b7cb", + "3044d109-f028-4489-b59c-f267afe408f2", + "997b0851-37c5-4d65-a477-a8b3a56eea42", + "4691e516-6989-4a16-8f55-12ff2226a3c9", + "98dfd0fd-504e-4280-8e73-6983a6af1b8c", + "1b8044f6-1d25-4a83-9662-acdf404341d2" + ], + "OtherVersionVoters": [ + "79324811-9588-4311-b208-f272e38aaabf", + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d", + "4c42fe86-321d-4e8f-be0d-c261e6cfc453" + ], + "OtherVersionNonVoters": [ + "6ca5a41c-6162-41c1-b4eb-09082efe206f", + "1baeb453-ad9e-489a-bbfe-53bee097aec8", + "746b8782-ce77-41fd-bce0-cce62cca62b4" + ], + "OtherVersionReadReplicas": [ + "7b02a615-ccce-4251-bda8-b89e0bd4f7c7" + ], + "RedundancyZones": { + "zone1": { + "TargetVersionNonVoters": [ + "de95799e-15a4-4c86-b508-78840554b7cb", + "3044d109-f028-4489-b59c-f267afe408f2" + ], + "OtherVersionVoters": [ + "79324811-9588-4311-b208-f272e38aaabf" + ], + "OtherVersionNonVoters": [ + "6ca5a41c-6162-41c1-b4eb-09082efe206f" + ] + }, + "zone2": { + "TargetVersionNonVoters": [ + "997b0851-37c5-4d65-a477-a8b3a56eea42", + "4691e516-6989-4a16-8f55-12ff2226a3c9" + ], + "OtherVersionVoters": [ + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d" + ], + "OtherVersionNonVoters": [ + "1baeb453-ad9e-489a-bbfe-53bee097aec8" + ] + }, + "zone3": { + "TargetVersionNonVoters": [ + "98dfd0fd-504e-4280-8e73-6983a6af1b8c", + "1b8044f6-1d25-4a83-9662-acdf404341d2" + ], + "OtherVersionVoters": [ + "4c42fe86-321d-4e8f-be0d-c261e6cfc453" + ], + "OtherVersionNonVoters": [ + "746b8782-ce77-41fd-bce0-cce62cca62b4" + ] + } + } + } +} \ No newline at end of file diff --git a/command/operator/autopilot/state/testdata/enterprise/pretty.golden b/command/operator/autopilot/state/testdata/enterprise/pretty.golden new file mode 100644 index 000000000..c189f8773 --- /dev/null +++ b/command/operator/autopilot/state/testdata/enterprise/pretty.golden @@ -0,0 +1,279 @@ +Healthy: true +Failure Tolerance: 1 +Optimistic Failure Tolerance: 0 +Leader: 79324811-9588-4311-b208-f272e38aaabf +Voters: + 79324811-9588-4311-b208-f272e38aaabf + 6e8a37e5-a1c4-4212-a75e-91487d8cda6d + 4c42fe86-321d-4e8f-be0d-c261e6cfc453 +Read Replicas: + 7b02a615-ccce-4251-bda8-b89e0bd4f7c7 +Redundancy Zones: + zone1: + Failure Tolerance: 3 + Voters: + 79324811-9588-4311-b208-f272e38aaabf + Servers: + 79324811-9588-4311-b208-f272e38aaabf + 6ca5a41c-6162-41c1-b4eb-09082efe206f + de95799e-15a4-4c86-b508-78840554b7cb + 3044d109-f028-4489-b59c-f267afe408f2 + zone2: + Failure Tolerance: 3 + Voters: + 6e8a37e5-a1c4-4212-a75e-91487d8cda6d + Servers: + 6e8a37e5-a1c4-4212-a75e-91487d8cda6d + 1baeb453-ad9e-489a-bbfe-53bee097aec8 + 997b0851-37c5-4d65-a477-a8b3a56eea42 + 4691e516-6989-4a16-8f55-12ff2226a3c9 + zone3: + Failure Tolerance: 3 + Voters: + 4c42fe86-321d-4e8f-be0d-c261e6cfc453 + Servers: + 4c42fe86-321d-4e8f-be0d-c261e6cfc453 + 746b8782-ce77-41fd-bce0-cce62cca62b4 + 98dfd0fd-504e-4280-8e73-6983a6af1b8c + 1b8044f6-1d25-4a83-9662-acdf404341d2 +Upgrade: + Status: promoting + Target Version: 2.0.0 + Target Version Non-Voters: + de95799e-15a4-4c86-b508-78840554b7cb + 3044d109-f028-4489-b59c-f267afe408f2 + 997b0851-37c5-4d65-a477-a8b3a56eea42 + 4691e516-6989-4a16-8f55-12ff2226a3c9 + 98dfd0fd-504e-4280-8e73-6983a6af1b8c + 1b8044f6-1d25-4a83-9662-acdf404341d2 + Other Version Voters: + 79324811-9588-4311-b208-f272e38aaabf + 6e8a37e5-a1c4-4212-a75e-91487d8cda6d + 4c42fe86-321d-4e8f-be0d-c261e6cfc453 + Other Version Non-Voters: + 6ca5a41c-6162-41c1-b4eb-09082efe206f + 1baeb453-ad9e-489a-bbfe-53bee097aec8 + 746b8782-ce77-41fd-bce0-cce62cca62b4 + Other Version ReadReplicas: + 7b02a615-ccce-4251-bda8-b89e0bd4f7c7 +Servers: + 1b8044f6-1d25-4a83-9662-acdf404341d2 + Name: node6.new + Address: 198.18.1.6:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 41 + Redundancy Zone: zone3 + Upgrade Version: 2.0.0 + Meta + "bar": "baz" + "upgrade": "2.0.0" + "zone": "zone3" + 1baeb453-ad9e-489a-bbfe-53bee097aec8 + Name: node4 + Address: 198.18.0.4:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 41 + Redundancy Zone: zone2 + Upgrade Version: 1.0.0 + Meta + "bar": "baz" + "upgrade": "1.0.0" + "zone": "zone2" + 3044d109-f028-4489-b59c-f267afe408f2 + Name: node2.new + Address: 198.18.1.2:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 41 + Redundancy Zone: zone1 + Upgrade Version: 2.0.0 + Meta + "bar": "baz" + "upgrade": "2.0.0" + "zone": "zone1" + 4691e516-6989-4a16-8f55-12ff2226a3c9 + Name: node4.new + Address: 198.18.1.4:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 41 + Redundancy Zone: zone2 + Upgrade Version: 2.0.0 + Meta + "bar": "baz" + "upgrade": "2.0.0" + "zone": "zone2" + 4c42fe86-321d-4e8f-be0d-c261e6cfc453 + Name: node5 + Address: 198.18.0.5:8300 + Version: 1.9.0 + Status: voter + Node Type: zone-voter + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 42 + Redundancy Zone: zone3 + Upgrade Version: 1.0.0 + Meta + "foo": "bar" + "upgrade": "1.0.0" + "zone": "zone3" + 6ca5a41c-6162-41c1-b4eb-09082efe206f + Name: node2 + Address: 198.18.0.2:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 41 + Redundancy Zone: zone1 + Upgrade Version: 1.0.0 + Meta + "bar": "baz" + "upgrade": "1.0.0" + "zone": "zone1" + 6e8a37e5-a1c4-4212-a75e-91487d8cda6d + Name: node3 + Address: 198.18.0.3:8300 + Version: 1.9.0 + Status: voter + Node Type: zone-voter + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 42 + Redundancy Zone: zone2 + Upgrade Version: 1.0.0 + Meta + "foo": "bar" + "upgrade": "1.0.0" + "zone": "zone2" + 746b8782-ce77-41fd-bce0-cce62cca62b4 + Name: node6 + Address: 198.18.0.6:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 41 + Redundancy Zone: zone3 + Upgrade Version: 1.0.0 + Meta + "bar": "baz" + "upgrade": "1.0.0" + "zone": "zone3" + 79324811-9588-4311-b208-f272e38aaabf + Name: node1 + Address: 198.18.0.1:8300 + Version: 1.9.0 + Status: leader + Node Type: zone-voter + Node Status: alive + Healthy: true + Last Contact: 0s + Last Term: 3 + Last Index: 42 + Redundancy Zone: zone1 + Upgrade Version: 1.0.0 + Meta + "foo": "bar" + "upgrade": "1.0.0" + "zone": "zone1" + 7b02a615-ccce-4251-bda8-b89e0bd4f7c7 + Name: read-replica + Address: 198.18.0.7:8300 + Version: 1.9.0 + Status: non-voter + Node Type: read-replica + Node Status: alive + Healthy: true + Last Contact: 2ms + Last Term: 3 + Last Index: 39 + Upgrade Version: 1.0.0 + Read Replica: true + Meta + "baz": "foo" + "version": "1.0.0" + 98dfd0fd-504e-4280-8e73-6983a6af1b8c + Name: node5.new + Address: 198.18.1.5:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 42 + Redundancy Zone: zone3 + Upgrade Version: 2.0.0 + Meta + "foo": "bar" + "upgrade": "2.0.0" + "zone": "zone3" + 997b0851-37c5-4d65-a477-a8b3a56eea42 + Name: node3.new + Address: 198.18.1.3:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 42 + Redundancy Zone: zone2 + Upgrade Version: 2.0.0 + Meta + "foo": "bar" + "upgrade": "2.0.0" + "zone": "zone2" + de95799e-15a4-4c86-b508-78840554b7cb + Name: node1.new + Address: 198.18.1.1:8300 + Version: 1.9.0 + Status: non-voter + Node Type: zone-standby + Node Status: alive + Healthy: true + Last Contact: 0s + Last Term: 3 + Last Index: 42 + Redundancy Zone: zone1 + Upgrade Version: 2.0.0 + Meta + "foo": "bar" + "upgrade": "2.0.0" + "zone": "zone1" diff --git a/command/operator/autopilot/state/testdata/enterprise/state.json b/command/operator/autopilot/state/testdata/enterprise/state.json new file mode 100644 index 000000000..1536a031d --- /dev/null +++ b/command/operator/autopilot/state/testdata/enterprise/state.json @@ -0,0 +1,389 @@ +{ + "Healthy": true, + "FailureTolerance": 1, + "OptimisitcFailureTolerance": 10, + "Servers": { + "1b8044f6-1d25-4a83-9662-acdf404341d2": { + "ID": "1b8044f6-1d25-4a83-9662-acdf404341d2", + "Name": "node6.new", + "Address": "198.18.1.6:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone3", + "UpgradeVersion": "2.0.0", + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "2.0.0", + "zone": "zone3" + }, + "NodeType": "zone-standby" + }, + "1baeb453-ad9e-489a-bbfe-53bee097aec8": { + "ID": "1baeb453-ad9e-489a-bbfe-53bee097aec8", + "Name": "node4", + "Address": "198.18.0.4:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone2", + "UpgradeVersion": "1.0.0", + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "1.0.0", + "zone": "zone2" + }, + "NodeType": "zone-standby" + }, + "3044d109-f028-4489-b59c-f267afe408f2": { + "ID": "3044d109-f028-4489-b59c-f267afe408f2", + "Name": "node2.new", + "Address": "198.18.1.2:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone1", + "UpgradeVersion": "2.0.0", + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "2.0.0", + "zone": "zone1" + }, + "NodeType": "zone-standby" + }, + "4691e516-6989-4a16-8f55-12ff2226a3c9": { + "ID": "4691e516-6989-4a16-8f55-12ff2226a3c9", + "Name": "node4.new", + "Address": "198.18.1.4:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone2", + "UpgradeVersion": "2.0.0", + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "2.0.0", + "zone": "zone2" + }, + "NodeType": "zone-standby" + }, + "4c42fe86-321d-4e8f-be0d-c261e6cfc453": { + "ID": "4c42fe86-321d-4e8f-be0d-c261e6cfc453", + "Name": "node5", + "Address": "198.18.0.5:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone3", + "UpgradeVersion": "1.0.0", + "Status": "voter", + "Meta": { + "foo": "bar", + "upgrade": "1.0.0", + "zone": "zone3" + }, + "NodeType": "zone-voter" + }, + "6ca5a41c-6162-41c1-b4eb-09082efe206f": { + "ID": "6ca5a41c-6162-41c1-b4eb-09082efe206f", + "Name": "node2", + "Address": "198.18.0.2:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone1", + "UpgradeVersion": "1.0.0", + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "1.0.0", + "zone": "zone1" + }, + "NodeType": "zone-standby" + }, + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d": { + "ID": "6e8a37e5-a1c4-4212-a75e-91487d8cda6d", + "Name": "node3", + "Address": "198.18.0.3:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone2", + "UpgradeVersion": "1.0.0", + "Status": "voter", + "Meta": { + "foo": "bar", + "upgrade": "1.0.0", + "zone": "zone2" + }, + "NodeType": "zone-voter" + }, + "746b8782-ce77-41fd-bce0-cce62cca62b4": { + "ID": "746b8782-ce77-41fd-bce0-cce62cca62b4", + "Name": "node6", + "Address": "198.18.0.6:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "RedundancyZone": "zone3", + "UpgradeVersion": "1.0.0", + "Status": "non-voter", + "Meta": { + "bar": "baz", + "upgrade": "1.0.0", + "zone": "zone3" + }, + "NodeType": "zone-standby" + }, + "79324811-9588-4311-b208-f272e38aaabf": { + "ID": "79324811-9588-4311-b208-f272e38aaabf", + "Name": "node1", + "Address": "198.18.0.1:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "0s", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone1", + "UpgradeVersion": "1.0.0", + "Status": "leader", + "Meta": { + "foo": "bar", + "upgrade": "1.0.0", + "zone": "zone1" + }, + "NodeType": "zone-voter" + }, + "7b02a615-ccce-4251-bda8-b89e0bd4f7c7": { + "ID": "7b02a615-ccce-4251-bda8-b89e0bd4f7c7", + "Name": "read-replica", + "Address": "198.18.0.7:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "2ms", + "LastTerm": 3, + "LastIndex": 39, + "Healthy": true, + "StableSince": "2020-11-06T14:53:00Z", + "UpgradeVersion": "1.0.0", + "ReadReplica": true, + "Status": "non-voter", + "Meta": { + "baz": "foo", + "version": "1.0.0" + }, + "NodeType": "read-replica" + }, + "98dfd0fd-504e-4280-8e73-6983a6af1b8c": { + "ID": "98dfd0fd-504e-4280-8e73-6983a6af1b8c", + "Name": "node5.new", + "Address": "198.18.1.5:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone3", + "UpgradeVersion": "2.0.0", + "Status": "non-voter", + "Meta": { + "foo": "bar", + "upgrade": "2.0.0", + "zone": "zone3" + }, + "NodeType": "zone-standby" + }, + "997b0851-37c5-4d65-a477-a8b3a56eea42": { + "ID": "997b0851-37c5-4d65-a477-a8b3a56eea42", + "Name": "node3.new", + "Address": "198.18.1.3:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone2", + "UpgradeVersion": "2.0.0", + "Status": "non-voter", + "Meta": { + "foo": "bar", + "upgrade": "2.0.0", + "zone": "zone2" + }, + "NodeType": "zone-standby" + }, + "de95799e-15a4-4c86-b508-78840554b7cb": { + "ID": "de95799e-15a4-4c86-b508-78840554b7cb", + "Name": "node1.new", + "Address": "198.18.1.1:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "0s", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "RedundancyZone": "zone1", + "UpgradeVersion": "2.0.0", + "Status": "non-voter", + "Meta": { + "foo": "bar", + "upgrade": "2.0.0", + "zone": "zone1" + }, + "NodeType": "zone-standby" + } + }, + "Leader": "79324811-9588-4311-b208-f272e38aaabf", + "Voters": [ + "79324811-9588-4311-b208-f272e38aaabf", + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d", + "4c42fe86-321d-4e8f-be0d-c261e6cfc453" + ], + "ReadReplicas": [ + "7b02a615-ccce-4251-bda8-b89e0bd4f7c7" + ], + "RedundancyZones": { + "zone1": { + "Servers": [ + "79324811-9588-4311-b208-f272e38aaabf", + "6ca5a41c-6162-41c1-b4eb-09082efe206f", + "de95799e-15a4-4c86-b508-78840554b7cb", + "3044d109-f028-4489-b59c-f267afe408f2" + ], + "Voters": [ + "79324811-9588-4311-b208-f272e38aaabf" + ], + "FailureTolerance": 3 + }, + "zone2": { + "Servers": [ + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d", + "1baeb453-ad9e-489a-bbfe-53bee097aec8", + "997b0851-37c5-4d65-a477-a8b3a56eea42", + "4691e516-6989-4a16-8f55-12ff2226a3c9" + ], + "Voters": [ + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d" + ], + "FailureTolerance": 3 + }, + "zone3": { + "Servers": [ + "4c42fe86-321d-4e8f-be0d-c261e6cfc453", + "746b8782-ce77-41fd-bce0-cce62cca62b4", + "98dfd0fd-504e-4280-8e73-6983a6af1b8c", + "1b8044f6-1d25-4a83-9662-acdf404341d2" + ], + "Voters": [ + "4c42fe86-321d-4e8f-be0d-c261e6cfc453" + ], + "FailureTolerance": 3 + } + }, + "Upgrade": { + "Status": "promoting", + "TargetVersion": "2.0.0", + "TargetVersionNonVoters": [ + "de95799e-15a4-4c86-b508-78840554b7cb", + "3044d109-f028-4489-b59c-f267afe408f2", + "997b0851-37c5-4d65-a477-a8b3a56eea42", + "4691e516-6989-4a16-8f55-12ff2226a3c9", + "98dfd0fd-504e-4280-8e73-6983a6af1b8c", + "1b8044f6-1d25-4a83-9662-acdf404341d2" + ], + "OtherVersionVoters": [ + "79324811-9588-4311-b208-f272e38aaabf", + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d", + "4c42fe86-321d-4e8f-be0d-c261e6cfc453" + ], + "OtherVersionNonVoters": [ + "6ca5a41c-6162-41c1-b4eb-09082efe206f", + "1baeb453-ad9e-489a-bbfe-53bee097aec8", + "746b8782-ce77-41fd-bce0-cce62cca62b4" + ], + "OtherVersionReadReplicas": [ + "7b02a615-ccce-4251-bda8-b89e0bd4f7c7" + ], + "RedundancyZones": { + "zone1": { + "TargetVersionNonVoters": [ + "de95799e-15a4-4c86-b508-78840554b7cb", + "3044d109-f028-4489-b59c-f267afe408f2" + ], + "OtherVersionVoters": [ + "79324811-9588-4311-b208-f272e38aaabf" + ], + "OtherVersionNonVoters": [ + "6ca5a41c-6162-41c1-b4eb-09082efe206f" + ] + }, + "zone2": { + "TargetVersionNonVoters": [ + "997b0851-37c5-4d65-a477-a8b3a56eea42", + "4691e516-6989-4a16-8f55-12ff2226a3c9" + ], + "OtherVersionVoters": [ + "6e8a37e5-a1c4-4212-a75e-91487d8cda6d" + ], + "OtherVersionNonVoters": [ + "1baeb453-ad9e-489a-bbfe-53bee097aec8" + ] + }, + "zone3": { + "TargetVersionNonVoters": [ + "98dfd0fd-504e-4280-8e73-6983a6af1b8c", + "1b8044f6-1d25-4a83-9662-acdf404341d2" + ], + "OtherVersionVoters": [ + "4c42fe86-321d-4e8f-be0d-c261e6cfc453" + ], + "OtherVersionNonVoters": [ + "746b8782-ce77-41fd-bce0-cce62cca62b4" + ] + } + } + } +} \ No newline at end of file diff --git a/command/operator/autopilot/state/testdata/oss/json.golden b/command/operator/autopilot/state/testdata/oss/json.golden new file mode 100644 index 000000000..59bf02c98 --- /dev/null +++ b/command/operator/autopilot/state/testdata/oss/json.golden @@ -0,0 +1,67 @@ +{ + "Healthy": true, + "FailureTolerance": 1, + "OptimisticFailureTolerance": 0, + "Servers": { + "79324811-9588-4311-b208-f272e38aaabf": { + "ID": "79324811-9588-4311-b208-f272e38aaabf", + "Name": "node1", + "Address": "198.18.0.1:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "0s", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "ReadReplica": false, + "Status": "leader", + "Meta": { + "foo": "bar" + }, + "NodeType": "voter" + }, + "ae84aefb-a303-4734-8739-5c102d4ee2d9": { + "ID": "ae84aefb-a303-4734-8739-5c102d4ee2d9", + "Name": "node3", + "Address": "198.18.0.3:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "2ms", + "LastTerm": 3, + "LastIndex": 39, + "Healthy": true, + "StableSince": "2020-11-06T14:53:00Z", + "ReadReplica": false, + "Status": "voter", + "Meta": { + "baz": "foo" + }, + "NodeType": "voter" + }, + "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f": { + "ID": "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f", + "Name": "node2", + "Address": "198.18.0.2:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "ReadReplica": false, + "Status": "voter", + "Meta": { + "bar": "baz" + }, + "NodeType": "voter" + } + }, + "Leader": "79324811-9588-4311-b208-f272e38aaabf", + "Voters": [ + "79324811-9588-4311-b208-f272e38aaabf", + "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f", + "ae84aefb-a303-4734-8739-5c102d4ee2d9" + ] +} \ No newline at end of file diff --git a/command/operator/autopilot/state/testdata/oss/pretty.golden b/command/operator/autopilot/state/testdata/oss/pretty.golden new file mode 100644 index 000000000..24bfaeee3 --- /dev/null +++ b/command/operator/autopilot/state/testdata/oss/pretty.golden @@ -0,0 +1,48 @@ +Healthy: true +Failure Tolerance: 1 +Optimistic Failure Tolerance: 0 +Leader: 79324811-9588-4311-b208-f272e38aaabf +Voters: + 79324811-9588-4311-b208-f272e38aaabf + ef8aee9a-f9d6-4ec4-b383-aac956bdb80f + ae84aefb-a303-4734-8739-5c102d4ee2d9 +Servers: + 79324811-9588-4311-b208-f272e38aaabf + Name: node1 + Address: 198.18.0.1:8300 + Version: 1.9.0 + Status: leader + Node Type: voter + Node Status: alive + Healthy: true + Last Contact: 0s + Last Term: 3 + Last Index: 42 + Meta + "foo": "bar" + ae84aefb-a303-4734-8739-5c102d4ee2d9 + Name: node3 + Address: 198.18.0.3:8300 + Version: 1.9.0 + Status: voter + Node Type: voter + Node Status: alive + Healthy: true + Last Contact: 2ms + Last Term: 3 + Last Index: 39 + Meta + "baz": "foo" + ef8aee9a-f9d6-4ec4-b383-aac956bdb80f + Name: node2 + Address: 198.18.0.2:8300 + Version: 1.9.0 + Status: voter + Node Type: voter + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 41 + Meta + "bar": "baz" diff --git a/command/operator/autopilot/state/testdata/oss/state.json b/command/operator/autopilot/state/testdata/oss/state.json new file mode 100644 index 000000000..106ed6e07 --- /dev/null +++ b/command/operator/autopilot/state/testdata/oss/state.json @@ -0,0 +1,64 @@ +{ + "Healthy": true, + "FailureTolerance": 1, + "OptimisitcFailureTolerance": 0, + "Servers": { + "79324811-9588-4311-b208-f272e38aaabf": { + "ID": "79324811-9588-4311-b208-f272e38aaabf", + "Name": "node1", + "Address": "198.18.0.1:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "0s", + "LastTerm": 3, + "LastIndex": 42, + "Healthy": true, + "StableSince": "2020-11-06T14:51:00Z", + "Status": "leader", + "Meta": { + "foo": "bar" + }, + "NodeType": "voter" + }, + "ae84aefb-a303-4734-8739-5c102d4ee2d9": { + "ID": "ae84aefb-a303-4734-8739-5c102d4ee2d9", + "Name": "node3", + "Address": "198.18.0.3:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "2ms", + "LastTerm": 3, + "LastIndex": 39, + "Healthy": true, + "StableSince": "2020-11-06T14:53:00Z", + "Status": "voter", + "Meta": { + "baz": "foo" + }, + "NodeType": "voter" + }, + "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f": { + "ID": "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f", + "Name": "node2", + "Address": "198.18.0.2:8300", + "NodeStatus": "alive", + "Version": "1.9.0", + "LastContact": "1ms", + "LastTerm": 3, + "LastIndex": 41, + "Healthy": true, + "StableSince": "2020-11-06T14:52:00Z", + "Status": "voter", + "Meta": { + "bar": "baz" + }, + "NodeType": "voter" + } + }, + "Leader": "79324811-9588-4311-b208-f272e38aaabf", + "Voters": [ + "79324811-9588-4311-b208-f272e38aaabf", + "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f", + "ae84aefb-a303-4734-8739-5c102d4ee2d9" + ] +} \ No newline at end of file diff --git a/website/pages/commands/operator/autopilot.mdx b/website/pages/commands/operator/autopilot.mdx index 44977dc80..b8c34d559 100644 --- a/website/pages/commands/operator/autopilot.mdx +++ b/website/pages/commands/operator/autopilot.mdx @@ -29,7 +29,7 @@ Subcommands: ## get-config -This command displays the current Raft peer configuration. +This command displays the current autopilot configuration. Usage: `consul operator autopilot get-config [options]` @@ -39,9 +39,10 @@ Usage: `consul operator autopilot get-config [options]` @include 'http_api_options_server.mdx' -The output looks like this: +### Command Output -``` +```sh +$ consul operator autopilot get-config CleanupDeadServers = true LastContactThreshold = 200ms MaxTrailingLogs = 250 @@ -90,10 +91,81 @@ Usage: `consul operator autopilot set-config [options]` - `-upgrade-version-tag` - Controls the [`-node-meta`](/docs/agent/options#_node_meta) tag to use for version info when performing upgrade migrations. If left blank, the Consul version will be used. -The output looks like this: +### Command Output -``` +```sh +$ consul operator autopilot set-config -min-quorum=3 Configuration updated! ``` The return code will indicate success or failure. + +## state + +This command displays the current autopilot state. + +Usage: `consul operator autopilot state [options]` + +#### API Options + +@include 'http_api_options_client.mdx' + +@include 'http_api_options_server.mdx' + +#### Command Options + +- `-format` - Specifies the output format. Must be one of `[pretty|json]` and it defaults to `pretty`. + +#### Command Output + +```sh +$ consul operator autopilot state +Healthy: true +Failure Tolerance: 1 +Optimistic Failure Tolerance: 0 +Leader: 79324811-9588-4311-b208-f272e38aaabf +Voters: + 79324811-9588-4311-b208-f272e38aaabf + ef8aee9a-f9d6-4ec4-b383-aac956bdb80f + ae84aefb-a303-4734-8739-5c102d4ee2d9 +Servers: + 79324811-9588-4311-b208-f272e38aaabf + Name: node1 + Address: 198.18.0.1:8300 + Version: 1.9.0 + Status: leader + Node Type: voter + Node Status: alive + Healthy: true + Last Contact: 0s + Last Term: 3 + Last Index: 42 + Meta + "foo": "bar" + ae84aefb-a303-4734-8739-5c102d4ee2d9 + Name: node3 + Address: 198.18.0.3:8300 + Version: 1.9.0 + Status: voter + Node Type: voter + Node Status: alive + Healthy: true + Last Contact: 2ms + Last Term: 3 + Last Index: 39 + Meta + "baz": "foo" + ef8aee9a-f9d6-4ec4-b383-aac956bdb80f + Name: node2 + Address: 198.18.0.2:8300 + Version: 1.9.0 + Status: voter + Node Type: voter + Node Status: alive + Healthy: true + Last Contact: 1ms + Last Term: 3 + Last Index: 41 + Meta + "bar": "baz" +```